Java

[스레드] - sleep, join, wait, interrupt, notify, notifyAll, yield

jnk1m 2022. 9. 13. 18:11

일시 정지로 가기 위한 메소드 & 벗어나기 위한 메소드


구분 메서드 실행 
일시 정지로 보냄 sleep(long millis) 주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 
join() join() 메서드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태가 되려면, join() 메서드를 가진 스레드가 종료되어야 한다. 
wait() 동기화 블록 내에서 스레드를 일시 정지 상태로 만든다. 
일시 정지에서 벗어남 interrupt() 일시 정지 상태일 경우, interruptedException을 발생시켜 실행 대기 상태 또는 종료 상태로 만든다. 
notify()
notifyAll()
wait() 메서드로 인해 일시 정지 상태인 스레드를 실행 대기 상태로 만든다. 
실행 대기로 보냄  yield() 실행 상태에서 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다. 

 


 

일시 정지로 보내는 메소드 


sleep(long millis) 

주어진 시간 동안 쓰레드를 일시 정지. 

스레드를 timed_wait 상태로 만듬

 

정지 시간은 밀리세컨드 단위의 시간을 매개값으로 줘서 정해준다. 

주어진 시간이 지나면 자동으로 runnable 상태로 바껴서 실행 상태가 된다. 


일시 정지 상태에서는 InterruptedException이 발생할 수 있기 때문에 sleep()은 예외 처리가 필요한 메서드이다. 

//3초 주기로 비프음을 10회 발생시키는 예제 

import java.awt.*;

public class MyPrac1{
    public static void main(String[] args){
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for(int i =0 ; i<10; i++){
            toolkit.beep();
            try{
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
}

 

 


join(long millis) , join(long millis, int nanos)

다른 스레드의 종료를 기다림

join 메소드를 호출한 스레드는 wait 상태가 됨

join 메소드를 멤버로 가지는 스레드가 종료되거나, 파라미터로 주어진 시간이 지나면 Runnable 상태로 전이 

 

스레드는 다른 스레드와 독립적으로 실행하지만 다른 스레드가 종료될 때까지 기다렸다가 실행을 해야 하는 경우도 있다. 예를 들자면 계산 스레드의 작업이 종료된 후 그 결과값을 받아 처리하는 경우. 

 

  • join(): 호출한 스레드가 종료될 때까지 기다린다
  • join(long millis): 호출한 스레드를 지정된 시간 동안만 기다린다. 대기 시간은 밀리초 단위로 제어한다. 
  • join(long millis, int nanos): 호출한 스레드를 지정된 시간 동안만 기다린다. 대기 시간은 나노초 단위로 제어한다. 

 

일시 정지 상태에서는 InterruptedException이 발생할 수 있기 때문에 join()은 예외 처리가 필요한 메서드이다. 

 

public class MyPrac1 extends Thread{
    //MyPrac1 (sumThread)가 계산 작업을 모두 마칠 때까지 메인 스레드가 일시 정지 상태에 있다가
    //sumThread가 최종 계산된 결과값을 산출하고 종료하면 메인 스레드가 결과값을 받아 출력하는 예제
    private long sum;
    public long getSum(){
        return sum;
    }

    @Override
    public void run() {
        for(int i =1; i <= 100; i++){
            sum+= i;
        }
    }


}
public class MyPrac2 {
  public static void main(String[] args) {
    MyPrac1 sumThread = new MyPrac1();
    sumThread.start();
    try{
      sumThread.join();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println("1~100 합: "+sumThread.getSum());
  }
}

* join() 이 없으면 아직 연산이 끝나지 않은 상태에서 메인 스레드가 출력을 해버리기 때문에 결과로 0이 나온다. 메인 스레드를 기다리게 하기 위해 sleep()을 사용해도 이 경우엔 비슷한 효과가 있다. 


wait()

동기화 영역에서 락을 풀고 Wait-Set영역 (공유 객체별 존재)으로 이동시킨다.

 

 

 


실행대기로 보내는 메서드


yield()

다른 스레드에게 실행 양보

실행 중에 우선순위가 동일하거나 높은 순위의 스레드를 산출하고 Wait 상태로 전이 

  • static 메소드
  • 현재 실행 중인 스레드를 중지시켜 동일한 우선 순위의 다른 대기 스레드를 동작시킬 수 있다 
  • 동일한 우선 순위가 없는 경우, 다시 실행한다
  • 하나의 스레드가 프로세서르 ㄹ과도하게 점유하지 않도록 조절할 수 있다

 

yield()를 호출한 스레드는 실행 대기 상태로 돌아가고, 다른 스레드가 실행 상태가 된다. 

public void run(){
	while(true){
    	if(work){
        	System.out.println("ThreadA 작업 내용");
        } else{
        	Thread.yield();
        }
       }
     }

👆무의미한 반복을 하지 않고 다른 스레드에게 실행을 양보하도록 만든 간단한 예시

 

 

public class MyPrac1 extends Thread{
    public boolean work = true;

    //생성자
    public MyPrac1(String name){
        setName(name);
    }

    //메소드
    @Override
    public void run() {
        while(true){
            if(work){
                System.out.println(getName() + "작업 처리");
            }else {
                Thread.yield();
            }
        }
    }
}
public class MyPrac2 {
  public static void main(String[] args) {
    MyPrac1 workThreadA = new MyPrac1("workThreadA");
    MyPrac1 workThreadB = new MyPrac1("workThreadB");
    workThreadA.start();
    workThreadB.start();

    try{
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      workThreadA.work = false;
    }

    try{
      Thread.sleep(10000);
    }catch (InterruptedException e){
      workThreadA.work = true;
    }
  }
}

👆 처음 3초 동안은 ThreadA와 ThreadB가 번갈아 가며 실행하다가 3초 뒤에 메인 스레드가 ThreadA의 work 필드를 false로 변경함으로써 ThreadA가 yield() 메소드를 호출한다. 따라서 ThreadB가 더 많은 실행 기회를 얻게 된다. 

그리고 10초 뒤에 ThreadA의 work 필드를 true로 변경해 ThreadAdhk ThreadB가 다시 번갈아 가며 실행되도록 한다. 

 

 

일시 정지에서 벗어나는 메서드


interrupt()

스레드가 일시 정지 상태에 있을 때, InterruptException 예외를 발생시키는 역할을 한다. 

👉 Thread에서 stop(), suspend(), resume() 등의 동작 제어와 관련된 메소드는 안정성 문제로 인해 사용하지 말 것을 권고하고 있다. 기존 시스템과 호환성을 위해 남겨둘 뿐, 신규 개발에서는 interrupt 등을 통해 스레드에서 종료하도록 구성해야 한다. 


notify(), notifyAll()

  • Wait-Set 영역에 있는 스레드를 깨워서 실행할 수 있도록 한다.
  • notify()는 하나, notifyAll()은 Wait-Set에 있는 전부를 깨운다.
  • notify(): Wait-Set 영역에서 대기중인 스레드 중 랜덤하게 깨우므로, notifyAll() 크게 차이가 없다. 다만 notifyAll()의 경우, 모든 스레드가 깨어 났다 경쟁 후 하나를 제외한 나머지는 다시 대기 상태가 된다.