Java에는 동시성을 다루기 위해서 wait와 notify를 지원하지만 이 메서드들은 Java 5부터 지원하는 다양한 동시성 유틸리티 덕분에 wait와 notify를 사용할 이유가 줄었음
wait
갖고 있는 고유 락을 해제하고 스레드를 잠들게하는 메서드.
호출하는 메서드가 반드시 고유 락을 가지고 있어야 사용이 가능함
synchronized
블록 내부에서 호출이 되지 않으면 IllegalMonitorStateException
발생
notify
잠들어 있던 스레드 중 임의로 하나를 골라 깨우는 메서드.
단, notify는 잠들어 있는 스레드 중 어떤 스레드를 깨울지 선택할 수 없으므로 보통 notifyAll을 사용
notifyAll
잠들어 있던 스레드 모두를 깨우는 메서드
wait 예시
synchronized (obj) {
while(condition) {
obj.wait();
}
// ...
}
wait loop
내에서 호출을 해야하며 반복문 밖에서는 호출하면 안됨조건을 만족하지 않아도 스레드가 깨어날 수 있는 상황
notifyAll
로 모든 스레드를 깨울 수 있음notify
없이도 깨어날 수 있음 (허위 각성spurious wakeup
)public class Data {
private String packet;
private boolean transfer = true;
public synchronized void send(String packet) {
while (!transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
transfer = false;
this.packet = packet;
notifyAll();
}
public synchronized String receive() {
while (transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
transfer = true;
notifyAll();
return packet;
}
}
synchronized
키워드를 붙임으로써 락을 얻을 수 있도록 했음transfer
가 true일때는 send가 가능하고 false일때는 receive만 가능public class Sender implements Runnable {
private Data data;
public Sender(Data data) {
this.data = data;
}
@Override
public void run() {
String packets[] = {
"First packet",
"Second packet",
"Third packet",
"Fourth packet",
"End"
};
for (String packet : packets) {
data.send(packet);
// Thread.sleep() to mimic heavy server-side processing
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public class Receiver implements Runnable {
private Data load;
public Receiver(Data load) {
this.load = load;
}
public void run() {
for(String receivedMessage = load.receive();
!"End".equals(receivedMessage);
receivedMessage = load.receive()) {
System.out.println(receivedMessage);
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) {
Data data = new Data();
Thread sender = new Thread(new Sender(data));
Thread receiver = new Thread(new Receiver(data));
sender.start();
receiver.start();
}
Sender와 Receiver는 위와 같고 각각 Runnable을 구현하여 하나의 스레드에서 실행할 수 있도록 만들었음
⇒ 이를 만족하기 위해서 synchronized
와 wait
, notifyAll
을 올바르게 사용하는 것이 매우 까다롭고 복잡함 + synchronized
를 통한 락은 성능 저하도 일으킴