티스토리 뷰

  • 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() 를 호출 하기 전

더보기
Thread Dump 보기

"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 으로 진입한다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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
글 보관함