본문 바로가기
Language/Java

[Java] 쓰레드 5 - 쓰레드의 실행제어(sleep, interrupt, join, yield)

by 계범 2022. 3. 14.

쓰레드의 스케줄링 관련 메서드

메서드 설 명
static void sleep(long millis)
static void sleep(long millis, int nanos)
지정된 시간(천분의 1초 단위)동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. 
void join()
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
void interrupt() sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어나게 한다.
void stop() [안씀] 쓰레드를 즉시 종료시킨다.
void suspend() [안씀] 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다.
void resume() [안씀] suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.
resume(), stop(), suspend()는 쓰레드를 교착상태(dead-lock)로 만들기 쉽기 때문에 deprecated되었다.

2022.01.05 - [CS/OS(운영체제)] - 데드락(Deadlock)

 

쓰레드 상태

상태 설명
NEW 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE 실행 중 또는 실행 가능한 상태
BLOCKED 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
WAITING,
TIMED_WAITING
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태.
TIMED_WAITING은 일시정지시간이 지정된 경우를 의미.
TERMINATED 쓰레드의 작업이 종료된 상태
쓰레드의 상태는 Thread의 getState()메서드를 호출해서 확인 가능.

 

자바의 정석 책 참조

 

  1.  쓰레드를 생성하고 start() 호출하면 실행대기열에 들어감. 실행대기열은 큐와 같은 구조로 먼저 들어온 쓰레드가 먼저 실행.
  2. 자신의 차례가 되면 실행
  3. 주어진 실행시간이 다 되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 쓰레드가 실행.
  4. 실행 중에 4번에 적힌 것들에 의해 일시정지상태가 될 수 있음. I/O block은 입출력작업에서 발생하는 지연상태를 말한다.(입력값을 기다리는 작업 등. 입력 마치면 다시 실행대기상태가 됨.)
  5. 지정된 일시정지시간이 다되거나(time-out), 그외의 5번에 해당하는 명령어들이 호출되면 다시 실행대기열에 저장됨.
  6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸.

꼭 번호 순서대로 실행되는 것은 아님.

 

sleep()

sleep()은 지정된 시간동안 쓰레드를 멈추게 한다.

 

일정 쓰레드에 sleep()을 걸더라도, 항상 현재 실행중인 쓰레드에 대해 작동하기 때문에, thread1.sleep()을 걸더라도 현재 실행중인 쓰레드가 thread2라면 thread2가 지정된 시간동안 멈춘다.

 

그리고 sleep은 static으로 선언되어 있으며, 참조변수를 통해 호출하기보단 Thread.sleep(2000); 과 같이 해야 한다.

 

interrupt() 와 interrupted()

쓰레드는 자신의 run() 메서드가 모두 실행되면 자동적으로 종료된다.

하지만 실행중인 쓰레드를 종료하고 싶을 때가 있는데, 원래는 stop()이라는 메서드를 썼지만, deadlock 상황이 생길 수 있어 현재는 쓰지 않는다.

 

그럴 때 쓰는 것이 interrupt()이다.

sleep(), wait(), join()를 통해 일시정지상태에 있을때, interrupt() 메서드를 실행하여 InterruptedException을 발생시킨다.

반복조건문 내에 isInterrupted()를 통해서 호출여부를 확인하여 탈출하던가,

try-catch문을 통해 InterruptedException 발생을 잡아내어 try문에서 실행하던것에서 빠져나오면서 정상 종료된다.

 

void interrupt() : 쓰레드의 interrupted상태를 false에서 true로 변경.
boolean isInterrupted() : 쓰레드의 interrupted상태를 반환.
static boolean interrupted() : 현재 쓰레드의 interrupted상태를 반환 후, false로 변경.
쓰레드가 sleep(), wait(), join()에 의해 '일시정지 상태(WAITING)'에 있을 때, 해당 쓰레드에 대해 interrupt()를 호출하면, InterruptedException이 발생하고 쓰레드는 '실행대기 상태(RUNNABLE)'로 바뀐다.
즉, 멈춰있던 쓰레드를 깨워서 실행가능한 상태로 만드는 것이다.

 

import javax.swing.JOptionPane;

class ThreadEx13_1 {
	public static void main(String[] args) throws Exception 	{
		ThreadEx13_2 th1 = new ThreadEx13_2();
		th1.start();

		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");
		th1.interrupt();   // interrupt()를 호출하면, interrupted상태가 true가 된다.
		System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
	}
}

class ThreadEx13_2 extends Thread {
	public void run() {
		int i = 10;

		while(i!=0 && !isInterrupted()) {
			System.out.println(i--);
			for(long x=0;x<2500000000L;x++); // 시간 지연
		}

		System.out.println("카운트가 종료되었습니다.");
	} // main
}

위의 코드는 사용자의 입력이 끝나면 카운트다운이 중간에 멈추는 코드이다.

 

import javax.swing.JOptionPane;

class ThreadEx14_1 {
	public static void main(String[] args) throws Exception 	{
		ThreadEx14_2 th1 = new ThreadEx14_2();
		th1.start();

		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");
		th1.interrupt();   // interrupt()를 호출하면, interrupted상태가 true가 된다.
		System.out.println("isInterrupted():"+ th1.isInterrupted());
	}
}

class ThreadEx14_2 extends Thread {
	public void run() {
		int i = 10;

		while(i!=0 && !isInterrupted()) {
			System.out.println(i--);

			try {
				Thread.sleep(1000);  // 1초 지연
			} catch(InterruptedException e) {}
		}

		System.out.println("카운트가 종료되었습니다.");
	} // main
}

위의 코드는 이전 예제에서 시간지연을 위해 사용되었던 for문 대신 Thread.sleep(1000)으로 변경했는데,

사용자의 입력이 끝나더라도 종료되지 않을 수 있다.

 

이유는 interrupt()를 통해 interrupted상태가 true로 변환했지만,

try-catch문의 sleep()에 멈춰있을때 호출하면 InterruptedException이 발생하고 다시 false로 자동 초기화하기 때문이다.

try문 안에 while문을 넣었으면, 종료되었을 것이다.

 

yield()

자신에게 주어진 실행시간을 포기하고 다음 차례의 스레드에게 양보하는 메서드이다.

 

예시로 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0,5초 작업한 상태에서 yield()가 호출되면, 나머지 0.5초는 포기하고 다시 실행대기상태가 된다.

yield()와 interrupt()를 적절히 사용하면, 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있다.

 

join()

쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join()을 사용한다.

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

시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.

작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때 join()을 사용한다.

try{
	th1.join(); // 현재 실행중인 쓰레드가 th1쓰레드의 작업이 끝날때까지 기다린다.
} catch(InterruptedException e){}

 

join()도 interrupt()에 의해 대기상태에서 벗어날 수 있으며, try-catch문으로 감싸야한다.

sleep()과 다른점은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작한다.(sleep()은 static메서드로 현재 실행중인 쓰레드에 동작)

 

참조

'Java의 정석' 책

댓글