Java 파트12-2. 스레드 제어

1. 시작하기 전에


스레드 객체를 생성하고 start() 메소드를 호출하면 바로 실행되는 것이 아니라 실행 대기 상태가 된다. 운영체제는 실행 대기 상테에 있는 스레드 중 하나를 선택해서 실행 상태로 만든다. 실행 상태의 스레드는 run() 메소드가 끝나기 전에 다시 실행 대기 상태로 돌아갈 수도 있고, 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 되기도 한다. 즉, 스레드는 실행 대기 상태와 실행 상태를 번갈아가면서 자신의 run() 메소드를 조금씩 실행한다.


2. 스레드 상태


  • 실행 대기 상태 : start() 메소드를 호출하고 실행을 기다리고 있는 상태
  • 실행 상태 : 운영체제가 하나의 스레드를 선택하여 cpu(코어)가 run() 메소드를 실행한 상태
  • 종료 상태 : 실행 상태에서 run() 메소드가 종료되어 실행이 멈추게 되는 상태


3. 스레드 상태 제어


스레드 상태 제어란 실행 중인 스레드의 상태를 변경하는 것을 말한다. 스레드 상태 제어는 주어진 시간 동안 일시 정지시키는 sleep메소드, 스레드를 안전하게 종료기키는 stop 플래그, interrupt 메소드를 사용한다.

  • interrupt() : 일시 정지 상태의 스레드에서 InterruptedException을 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 보내거나 종료 상태로 보낼 수 있도록 한다.
  • sleep() : 주어진 시간 동안 스레드를 일시 정지 상태로 만들고, 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다.
  • stop() : 스레드를 즉시 종료한다. 불안정한 종료를 유발하므로 사용하지 않는게 좋다.


일시 정지

try {
    Thread.sleep(1000); // 1초 일시 정지    
} catch(InterruptedException e){
    // interrupt() 메소드가 호출되면 실행
}

일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException이 발생하기 때문에 예외처리가 필요하다. 위 코드는 1초동안 일시 정지 상태가 되고 1초가 지나면 다시 실행 준비 상태로 돌아간다.

안전한 종료

스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료되나 실행 중간에 즉시 종료해야하는 경우도 있다. 즉시 종료를 위해 stop메소드가 제공되지만 자원이 불안정한 상태로 남겨지기 때문에 이제는 사용하지 않고 stop 플래그를 사용한다.

// 메인
package sec02.exam02;

public class StopFlagExample {
	public static void main(String[] args)  {
		PrintThread printThread = new PrintThread();
		printThread.start(); // 작업 스레드 시작
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		
		printThread.setStop(true); // 작업 스레드 필드값 수정
	}
}

// 스레드 상속 하위 클래스 
package sec02.exam02;

public class PrintThread extends Thread {
	private boolean stop; // 스탑 플래그
	
	public void setStop(boolean stop) {
	  this.stop = stop;
	}
	
	public void run() {	
		while(!stop) { // 1초후 stop값이 수정되어 while문 종료
			System.out.println("실행 중");
		}	
		System.out.println("자원 정리 중");
		System.out.println("실행 종료");
	}
}

위 코드는 작업 스레드가 실행하는 도중에 1초후에 작업을 종료하는 코드이다. while 문으로 작업 스레드의 작업이 계속 되다가 메인에서 1초후에 작업 스레드의 필드값을 수정하여 작업 스레드의 while문을 종료시키는 것이다.

interrupt() 메소드 사용법

위에서 말했듯이 interrupt 메소드는 스레드가 일시 정지 상태에 있을 때 InterruprtedException을 발생시키는 역할을 한다. 이를 이용하면 run 메소드를 정상 종료할 수 있다.

// 메인
package sec02.exam03;

public class InterruptExample {
	public static void main(String[] args)  {
		Thread thread = new PrintThread(); // 스레드 객체 생성
		thread.start(); // 작업 스레드 실행 대기
		
		try {
			Thread.sleep(1000); // 현재 스레드 1초 일시 정지
		} catch (InterruptedException e) {
		}
		
		thread.interrupt(); // 작업 스레드에 interrupt 호출
	}
}

// Thread 상속 하위 클래스 
package sec02.exam03;

public class PrintThread extends Thread {
	public void run() {	
		try {
			while(true) {
				System.out.println("실행중");
				Thread.sleep(1); // InterruptedException 발생 -> catch 실행
			}	
		} catch(InterruptedException e) {}
		// 일시정지가 된 순간 catch를 실행하고 
        // 아래 print문이 실행된 후에 run이 종료된다.
        // 즉 실행 도중 일시정지가 발생하면 종료시키는 것이다.
		System.out.println("자원 정리");
		System.out.println("실행 종료");
	}
}

interrupt 메소드는 실행 즉시 InterruptedException을 발생시키는 것이 아니라 미래에 일시 정지 상태가 되면 InterruptedException을 발생시킨다. 즉, 반드시 일시 정지 상태가 되어야만 InterruptedException을 발생시며 스레드가 일시 정지 상태가 되지 않으면 아무런 의미가 없다.
interrupt 메소드의 호출 여부를 알 수 있는 방법은 다음과 같다.

// 정적 메소드로 현재 스레드가 interrupted 되었는지 확인 -> true 리턴
boolean status = Thread.interrupted();

// 인스턴스 메소드로 현재 스레드가 interrupted 되었는지 확인
boolean status = objThread.isInterrupted();


4. 데몬 스레드


데몬 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료된다.(보조 역할이기 때문) 이 점을 제외하면 그냥 일반 스레드랑 같다. 예를 들면, 워드프로세서의 자동 저장, 미디어 플레이어의 동영상 및 음악 재생 등이 있다.
데몬 스레드를 만들기 위해서는 주 스레드에서 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다. 3초동안 동작하는 메인 스레드에 1초마다 자동으로 저장하는 데몬 스레드를 만들어보자.

// 메인
package sec02.exam05;

public class DaemonExample {
	public static void main(String[] args) {
		AutoSaveThread autoSaveThread = new AutoSaveThread(); // 스레드 객체 생성
		autoSaveThread.setDaemon(true); // 스레드를 데몬 스레드로 , 주 스레드는 main
		autoSaveThread.start(); // 스레드 시작
		
		try {
			Thread.sleep(3000); // 3초 정지
		} catch (InterruptedException e) {
		}
		
        // 메인이 종료되면서 데몬스레드도 자동 종료
		System.out.println("메인 스레드 종료"); 
	}
}
// Thread 상속 하위 클래스
package sec02.exam05;

public class AutoSaveThread extends Thread {
	public void save() {
		System.out.println("저장 완료");
	}
	
	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				break;
			}
			save();
		}
	}
}


5. 정리하기


  • 스레드 상태 : 스레드는 생성하고 시작하면 다양한 상태를 가질 수 있다.
  • 일시 정지 : 실행 중인 스레드를 일정 시간 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 사용, 스레드는 주어진 시간동안 일시 정지가 되었다가 실행 대기 상태로 돌아간다.
  • 안정한 종료 : stop 플래그를 이용하거나 interrupt() 메소드를 이용
  • 데몬 스레드 : 주 스레드의 작업을 돕는 보조역할 스레드, 주 스레드 종료시 자동 종료



© 2021. By Backtony