티스토리 뷰
- Object.wait() : 현재 Object Monitor 를 획득하고 있는 Thread 의 Monitor 소유권을 해제하고 Thread 를 WAITING 상태로 만든다. wait() 메서드는 synchronzied 블록 안에서만 호출 가능하다. 중요한 것은 Lock 을 해제하고 Thread 가 WAITING 상태가 된다는 것!
- Object.notify() : 해당 Object Montior 의 WAITING 상태인 Thread 들 중 하나를 깨운다. 깨어난 Thread 는 Lock 을 얻기 위한 경쟁 상태인 BLOCKED 상태가 된다.
- Object.notifyAll() : 해당 Object Monitor 의 WAITING 상태인 Thread 들 전부를 깨운다. notify() 와 마찬가지로 깨어난 Thread Lock 을 얻기 위한 경쟁 상태인 BLOCKED 상태가 된다.
자세한 Thread 상태를 확인하기 전에 Java Montior 에 대한 내용을 먼저 익혀보자
위 그림은 Java Montior 의 개념을 추상적으로 설명하는 그림이다.
- Entry Set : 최초로 Lock 을 얻기 위해 대기 중인 Thread
- Wait Set : Lock 을 얻었다가 wait() 메서드가 호출되어 notify() 를 기다리고 있는 Thread
- The Owner : Lock 을 획득하여 활성화 되어있는 Thread.
주의해야할 점은 위 그림에서 Waiting Thread 로 표시된 Thread 의 상태가 WAITING 만 있는 것이 아니라는 것이다. 이 내용은 아래에서 설명한다.
Lock 을 획득하여 수행중인 Thread 가 (5) relase and exit 이후에 (2) acquire 에 의해서 Entry Set 의 Thread 중 하나의 Thread 가 Lock 을 얻을 수도 있고 (4) acquire 에 의해서 Wait Set 에 있는 Thread 중 하나의 Thread 가 Lock 을 얻을 수 있다.
위 예제 코드에서 getRequest() 함수에 synchronized 가 걸려있으므로 getRequest() 를 호출한 Thread 가 Lock 을 얻지 못한 경우에 Entry Set 에서 대기 중일 것이다.
Lock 을 얻어서 수행하다가 Line.11 에서 wait() 메서드가 호출되면서 (3) release 가 되면서 해당 Thread 는 Wait Set 으로 빠지게 되고 notify() 가 호출될때까지 대기하게 된다.
이제 위와 같이 Java Monitor 가 동작할때 Thread 상태는 어떻게 변화하는지 확인해본다.
예제 코드 설명
아래 예제는 Guarded-Suspension 패턴의 대표적인 예제이다. Guarded-Suspension 패턴에서 wait() 과 notify() 를 사용하기 때문에 해당 예제를 사용하여 Thread 상태가 어떻게 변화하는지 확인한다.
- Client Thread 는 RequestQueue.putRequest() 를 호출하여 Request 를 넣는다
- Server Thread 는 RequestQueue.getRequest() 를 호출하여 Request 를 꺼낸다.
- 각 Client 와 Server Thread 는 RequestQueue 객체를 공유한다.
- RequestQueue.getRequest() 와 RequestQueue.putRequest() 를 synchronized 가 붙어서 Lock 을 획득해야하만 진입할 수 있다.
ServerThread.java
public class ServerThread extends Thread {
private final Random random;
private final RequestQueue requestQueue;
public ServerThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
for (int i = 0; i < 1000; i ++) {
Request request = requestQueue.getRequest();
System.out.println(Thread.currentThread().getName() + " handles " + request);
}
}
}
ClientThread.java
public class ClientThread extends Thread {
private final RequestQueue requestQueue;
public ClientThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
for (int i = 0 ; i < 10000 ; i ++) {
Request request = new Request("No. " + i);
System.out.println(Thread.currentThread().getName() + " requests " + request);
requestQueue.putRequest(request);
}
}
}
RequestQueue.java
import java.util.LinkedList;
import java.util.Queue;
public class RequestQueue {
private final Queue<Request> queue = new LinkedList<>();
public synchronized Request getRequest() {
while (queue.peek() == null) {
try {
wait();
} catch (InterruptedException e) { }
}
return queue.remove();
}
public synchronized void putRequest(final Request request) {
queue.offer(request);
notifyAll();
}
}
Request.java
public class Request {
private final String name;
public Request(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Request{" +
"name='" + name + '\'' +
'}';
}
}
Main.java
public class Main {
public static void main(String[] args) {
RequestQueue requestQueue = new RequestQueue();
new ServerThread(requestQueue, "Woongs-Server", 6775897L).start();
new ServerThread(requestQueue, "Bbong-Server", 6775897L).start();
new ClientThread(requestQueue, "Juns-Client", 3141592L).start();
}
}
이제 위 예제 코드를 실행하여 Break Point 를 잡으면서 Thread Dump 를 떠서 Thread 상태를 관찰한다.
1. Woongs-Server 와 Bbong-Server Thread 가 RequestQueue.getRequest() 를 호출 하기 전
"Bbong-Server@481" prio=5 tid=0x16 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at ServerThread.run(ServerThread.java:16)
"Woongs-Server@480" prio=5 tid=0x15 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at ServerThread.run(ServerThread.java:16)
두 Server Thread 모두 RUNNABLE 상태로 CPU 의 점유를 받으면 실행가능한 상태이다. RequestQueue.getRequest() 를 호출하지 않았기 때문에 Entry Set 에 진입하지 않은 상태이다.
2. Woongs-Server Thread 가 먼저 RequestQueue.getRequest() 를 호출
"Woongs-Server@480" prio=5 tid=0x15 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks Bbong-Server@481
at RequestQueue.getRequest(RequestQueue.java:9)
- locked <0x1e3> (a RequestQueue)
at ServerThread.run(ServerThread.java:16)
"Bbong-Server@481" prio=5 tid=0x16 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Woongs-Server@480 to release lock on <0x1e3> (a RequestQueue)
at RequestQueue.getRequest(RequestQueue.java:9)
at ServerThread.run(ServerThread.java:16)
Woongs-Server Thread 가 먼저 RequestQueue.getRequest() 를 호출하여 Lock 을 획득하였다. 따라서 Bbong-Server Thread 는 BLOCKED 상태로 Woongs-Server 가 lock 을 해제하기를 기다리고 있다.
3. Woongs-Server , Bbong-Server Thread 모두 wait() 이 호출되어 notify() 를 기다리는 상태
"Juns-Client@506" prio=5 tid=0x17 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks Woongs-Server@480
blocks Bbong-Server@481
at RequestQueue.putRequest(RequestQueue.java:20)
- locked <0x1e3> (a RequestQueue)
at ClientThread.run(ClientThread.java:20)
"Bbong-Server@481" prio=5 tid=0x16 nid=NA waiting
java.lang.Thread.State: WAITING
waiting for Juns-Client@506 to release lock on <0x1e3> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
"Woongs-Server@480" prio=5 tid=0x15 nid=NA waiting
java.lang.Thread.State: WAITING
waiting for Juns-Client@506 to release lock on <0x1e3> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
두 Server Thread 는 RequestQueue.getRequest() 를 실행한 후에 처리할 Request 가 없어서 wait() 이 호출된 상태이다.
wait() 이 호출되고 WAITING 상태로 들어갔기 때문에 Lock 이 해제되어 Juns-Client Thread 가 Lock 을 획득하여 RequestQueue.putRequest() 를 실행할 수 있게 되었다.
4. Juns-Client Thread 가 notifyAll() 을 호출한 직후 (아직 Lock 해제 전)
"Bbong-Server@481" prio=5 tid=0x16 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Juns-Client@506 to release lock on <0x1e3> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
"Woongs-Server@480" prio=5 tid=0x15 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Juns-Client@506 to release lock on <0x1e3> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
"Juns-Client@506" prio=5 tid=0x17 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at RequestQueue.putRequest(RequestQueue.java:22)
- locked <0x1e3> (a RequestQueue)
at ClientThread.run(ClientThread.java:20)
Juns-Client Thread 가 호출한 RequestQueue.putRequest() 에서 notifyAll() 을 호출하여 WAITING 하고 있는 Thread 들을 깨웠지만 아직 Juns-Client Thread 가 잡고 있는 Lock 을 해제 하지 않은 상태이다.
WAITING 상태의 Woongs-Server, Bbong-Server Thread 가 WAITING -> BLOKCED 상태로 변경된걸 확인할 수 있다.
즉, WAITING 상태는 LOCK 을 획득하기 위한 경쟁 상태가 아니지만 notifyAll() 이 호출된 시점 부터 BLOCKED 상태로 변경되어 그 때부터 Lock 을 획득하기 위한 경쟁 상태가 되어 Juns-Client 가 Lock 을 해제하면 Lock 을 얻을 수 있는 상태로 변경된 것이다.
5. Juns-Client Thread 가 Lock 을 해제하여 BLOCKED 상태였던 Bbong-Server 가 Lock 을 획득한 상태
"Woongs-Server@480" prio=5 tid=0x15 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Bbong-Server@481 to release lock on <0x1e3> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
"Bbong-Server@481" prio=5 tid=0x16 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at RequestQueue.getRequest(RequestQueue.java:9)
- locked <0x1e3> (a RequestQueue)
at ServerThread.run(ServerThread.java:16)
"Juns-Client@506" prio=5 tid=0x17 nid=NA sleeping
java.lang.Thread.State: TIMED_WAITING
at java.lang.Thread.sleep(Thread.java:-1)
at ClientThread.run(ClientThread.java:23)
Juns-Client Thread 가 Lock 을 해제한 후에 BLOCKED 상태로 Lock 을 기다리던 Woongs-Server 와 Bbong-Server 가 경쟁하여 Bbong-Server 가 Lock 을 획득한 상태이다. 이때 Woongs-Server 는 여전히 BLOCKED 상태로 남아있는 것을 확인할 수 있다.
Juns-Client 는 Thread.sleep() 에 걸려 TIMED_WAITING 상태이다.
6. Bbong-Server Thread 가 Lock 을 획득하여 필요한 작업을 끝내고 Lock 을 해제한 상태
"Woongs-Server@480" prio=5 tid=0x15 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks Juns-Client@499
at RequestQueue.getRequest(RequestQueue.java:11)
- locked <0x1e2> (a RequestQueue)
at ServerThread.run(ServerThread.java:16)
"Juns-Client@499" prio=5 tid=0x17 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Woongs-Server@480 to release lock on <0x1e2> (a RequestQueue)
at RequestQueue.putRequest(RequestQueue.java:20)
at ClientThread.run(ClientThread.java:20)
"Bbong-Server@483" prio=5 tid=0x16 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at ServerThread.run(ServerThread.java:16)
Bbong-Server Thread 가 RequeseQueue.getRequest() 를 끝내고 Request 를 하나 가져간 상황.
Woongs-Server Thread 가 Lock 을 획득하여 BLOCKED -> RUNNABLE 상태로 변경되어 Lock 을 획득하여 수행중인 상태이다. Woongs-Server 가 Lock 을 획득할 수 있는 이유는 Bbong-Server 가 Lock 을 해제하고 Lock 과는 관계 없는 부분을 처리중이기 때문이다.
Juns-Client Thread 는 Woongs-Server Thread 가 획득한 Lock 을 대기하고 있으므로 BLOCKED. Juns-Client Thread 는 wait() 이 호출된 것이 아니므로 Entry Set 이다.
7. Woongs-Server Thread 는 처리할 Request 가 없으므로 wait() 호출된 상태
"Bbong-Server@483" prio=5 tid=0x16 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at java.lang.System.arraycopy(System.java:-1)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:128)
- locked <0x204> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:480)
- locked <0x205> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x207> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.newLine(PrintStream.java:546)
at java.io.PrintStream.println(PrintStream.java:807)
at ServerThread.run(ServerThread.java:17)
"Woongs-Server@480" prio=5 tid=0x15 nid=NA waiting
java.lang.Thread.State: WAITING
waiting for Juns-Client@499 to release lock on <0x1e2> (a RequestQueue)
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at RequestQueue.getRequest(RequestQueue.java:11)
at ServerThread.run(ServerThread.java:16)
"Juns-Client@499" prio=5 tid=0x17 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks Woongs-Server@480
at RequestQueue.putRequest(RequestQueue.java:20)
- locked <0x1e2> (a RequestQueue)
at ClientThread.run(ClientThread.java:20)
Woongs-Server 는 처리할 Request 가 없어서 wait() 호출되어 WAITING 상태가 되었다. Woongs-Server 가 해제한 Lock 을 받아 Juns-Client 가 수행중이다. Bbong-Server 는 Lock 과는 관계 없는 처리를 여전히 진행중이다.
이 상황에서 Bbong-Server 가 처리중인 일을 끝내고 다시 Lock 을 획득하기 위해서 RequestQueue.getRequest() 를 호출한다면 Entry Set 으로 들어가게 되고 BLOCKED 상태로 Juns-Client 가 Lock 을 해제할때까지 대기할 것이다.
정리
- Wait Entry 에 포함된 Thread 는 BLOCKED 상태일 수도 있고 WAITING 상태일 수도 있다.
- notify() 로 하나의 Thread 만 깨운 경우 깨어난 Thead 만 BLOCKED 나머지 Thread 는 WAITING
- notifyAll() 로 모든 Thread 를 깨운 경우 모든 Thread 가 BLOCKED
- notifyAll() 로 깨어나 BLOCKED 상태인 Thread 는 Lock 을 획득할때까지 BLOCKED 상태로 남아있고 WAITING 으로 돌아가지 않는다.
- Lock 을 모두 사용하고 release 된 Thread 는 Java Montior 에 관련이 없는 Thread 가 되고 다시 Lock 을 획득하려 할때 Entry set 으로 진입한다.
'JAVA' 카테고리의 다른 글
GC Log 로 확인하는 G1GC 동작 과정 (0) | 2023.10.20 |
---|---|
TLS (SSL Certificate) 와 Java TrustStore KeyStore (0) | 2023.02.10 |
StringBuilder 와 StringBuffer 비교 (0) | 2022.03.23 |
Heap 가상 메모리 address 범위 확인 (1) | 2022.01.04 |
JVM GC 후 Heap 에게 할당된 물리 메모리 OS에게 회수 여부 (0) | 2022.01.03 |
- Total
- Today
- Yesterday
- msyql-connector-java
- rate limit
- HashMap
- router
- reactor
- spring cloud gateway
- aurora
- RoutePredication
- reative
- Flux
- AbstractMethodError
- GlobalFilter
- referencedColumnName
- notifyAll()
- RouteDefinition
- getBoolean
- DyanomoDB
- mariadb-connector-j
- Seperate Chaining
- notify()
- circurit breaker
- dynamodb
- Lazy
- ConcurrentHashMap
- mariada-connector
- MariaDB
- wait()
- custom config data convertion
- ResultSet
- N+1
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |