[책펌] 스레드(쓰레드, Thread) 개념 잡기

By | 11월 6, 2008

* 스레드의 실행제어

- join() : 지정된 시간동안 스레드가 실행되도록 한다.(인자로 long millis를 넣었을때).
지정된 시간이 지나거나 작업이 종료되면 join()을 실행한 스레드로 다시 돌아와 실행을 계속한다.

- sleep() : 지정된 시간(long millis) 동안 스레드를 일시정지시킨다.
지정된 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다.
sleep()은 항상 현재 실행중인 스레드에 적용되기 때문에, t1.sleep(3000) 과 같은 코드가 있다고 해도, 현재 실행중인 스레드가 t1 이 아니라면 sleep()는 t1에 적용되지 않고, 현재 실행중인 스레드(예를 들면 main스레드)를 일시정지 시키게 된다. 그래서 sleep()은 참조변수를 통하여 호출하지 않고 Thread.sleep(3000)과 같이 실행하는 것이 보통이다.(yield()도 마찬가지)

- suspend() : 스레드를 일시정지 시킨다. resume()을 실행하면 다시 실행대기 상태가 된다.

-
stop() : 스레드를 즉시 종료 시킨다.

- resume() : suspend() 에 의해 일시정지 상태에 있는 스레드를 실행대기 상태로 만든다.

- yield() : 실행중에 다른 스레드에게 양보하고 실행대기 상태가 된다.

- interrupt() : sleep(), join(), wait()에 의해 일시정지 상태인 스레드를 실행대기 상태로 만든다. 해당 스레드에서는 InterruptedException이 발생함으로써 일시정지 상태를 벗어나게 된다. 그러나 interrupt() 가 호출되었을 때 sleep(), join(), wait() 에 의한 일시정지 상태가 아니라면 아무 일도 일어나지 않는다.

<참고> interrupt() 가 호출되어 실제로 InterruptedException이 발생했는지는 isInterrupted() 또는 interrupted() 를 통해서 알 수 있다.

* wait()과 notify() - 보다 효율적인 동기화를 위해서

* 개요

- 한 스레드가 객체에 lock을 걸고 어떤 조건이 만족될 때까지 기다려야 하는 경우, 이 스레드를 그대로 놔두면 이 객체를 사용하려는 다른 스레드들은 lock이 풀릴 때까지 기다려야 하는 상황이 발생한다.
이러한 비효율을 개선하기 위해서 wait()과 notify()를 사용한다. 한 스레드가 객체에 lock을 걸고 오래 기다리는 대신, wait()을 호출해서 다른 스레드에게 제어권을 넘겨주고 대기상태로 기다리다가 다른 스레드에 의해서 notify()가 호출되면 다시 실행상태가 되도록 하는 것이다. 이는 마치 식당에 자리가 날 때까지 서서 계속 기다리기보다 식당의 대기실에서 기다리고 있다가 자리가 나면 통보를 받는 것이 더 효율적인 것과 유사하다고 할 수 있다.

* 특징

- wait()과 notify()는 Thread 클래스가 아닌 Object 클래스에 정의된 메서드이다.,
- 동기화블록 내에서만 사용할 수 있다.

* 프로세스

- 스레드가 wait()을 호출하면 그 때까지 자신이 객체에 걸어 놓았던 모든 lock을 풀고, wait()이 호출된 객체의 대기실(waiting pool)에서 기다리게 된다. 그러다가 다른 스레드에 의해서 그 객체에 대해 notify()를 호출하면 객체의 대기실을 벗어나서 다시 실행대기상태, 즉 객체의 waiting pool을 벗어나 실행대기 열에서 자신이 실행될 차례를 기다리는 상태가 된다.

- notify()는 객체의 waiting pool에 있는 스레드 중의 하나만을 깨우고 notifyAll()은 모든 스레드를 깨운다. 어차피 한 번에 하나의 스레드만 객체를 사용할 수 있기 때문에 notify()를 사용하나 notifyAll()을 사용하나 별 차이는 없다. 그러나 notify()에 의해 어떤 스레드가 깨워지게 될지는 알 수 없어서 우선순위가 높은 특정 스레드가 오랫동안 객체의 waiting pool에 머물 수 있기 때문에 다시 객체의 waiting pool에 들어가더라도 notifyAll()을 이용해서 모든 스레드를 깨워놓고 JVM의 스레드스케쥴링에 의해서 처리되도록 하는 것이 안전하다.

- waiting pool은 객체마다 존재하는 것이기 때문에, notifyAll()이 호출된다고 해서 모든 객체의 waiting pool에 있는 스레드가 깨워지는 것은 아니다. notifyAll()이 호출되는 객체의 waiting pool에 대기 중인 스레드만 해당된다.

<참고> wait() 대신 wait(long timeout) 이나 wait(long  timeout, int nanos)를 사용하면 notify()가 호출되지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행상태가 된다.

* 예제

class Account{

   int balance = 1000;
   

/*
출금을 위해 withdraw()가 호출되었을 때 잔고가 부족하면 wait()을 호출해서 스레드가 객체의 lock을 풀고 그 객체의 waiting pool에 들어가면서 제어권을 다른 스레드에게 양보하게 된다.
*/

   public synchronized void withdraw(int money){
      while(balance < money){
          try{
              wait();
          }catch(InterruptedException e){} 
     }
   }//withdraw

/*
다른 스레드에 의해서 deposit() 메서드가 호출되어 잔고가 증가하면서 notify()를 호출하면 객체의 waiting pool에서 기다리고 있던 스레드를 깨우게 된다.
*/

   public synchronized void deposit(int money){
      balance -= money;
      notify();
   }

/*
notify()에 의해서 깨워진 스레드는 다시 withdraw()의 while문 조건식을 확인해서 잔고가 부족하면 다시 wait()을 호출하게 된다. 여기서 if문 대신 while 문을 사용한 이유는, 여러 스레드가 Account 객체를 공유하기 때문에 다시 깨어났을 때도 다시 한 번 조건을 확인해야 하기 때문이다.
*/

}//end class

- 출처 : [JAVA의 정석] 남궁성 저 -

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments