본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석10-4 데몬 쓰레드(daemon thread)와 쓰레드의 실행제어

by 이쟝 2022. 1. 13.

데몬 쓰레드(daemon thread)

- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할 수행

- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.

- GC(Garbage Collector), 자동저장, 화면 자동갱신 등에 사용된다.

- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.

- 데몬 쓰레드는 쓰레드를 생성한 다음 실행하기전에 setDaemon(true)를 호출하기만 하면 된다. 

 

boolean isDaemon( )  쓰레드가 데몬쓰레드인지 확인한다. 데몬 쓰레드이면 true를 반환
void setDaemon (boolean on)  쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경. 매개변수 on을 true로 지정하면 데몬쓰레드가 된다. 

 

*setDaemon(Boolean on)은 반드시 start()를 호출하기 전에 실행이 안되면 illegalThreadStateException이 발생한다.*

 

예제) 3초마다 변수 autoSave의 값을 확인해서 그 값이 true, autoSave( )를 호출하는 일을 무한히 반복하도록 쓰레드를 작성.

 

public class Ex13_7 implements Runnable { // Runnable 인터페이스를 구현!
	static boolean autoSave = false; // autoSave를 false로 초기화
	
	public static void main(String[] args) {
		// 일반 쓰레드
		Thread t = new Thread(new Ex13_7()); // Thread(Runnable r) Ex13_7은 run()구현해서 들어갈 수 있음
		t.setDaemon(true);  // 이 부분이 없으면 종료되지 않는다. 실행되기전에 set!
		t.start();
		
		for(int i=1; i<=15; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {} // 예외처리
			System.out.println(i + " ");
			
			if(i==5) autoSave = true;  // i가 5일 때 autoSave가 false -> true
		}
		System.out.println("프로그램을 종료합니다.");
	}
	
	// Daemon 쓰레드(일반쓰레드가 하나도 없을 때 자동종료)
	public void run() {
		while(true) {  // 무한 반복문
			try {
				Thread.sleep(3*1000); // 3초마다
			} catch(Exception e) { }
			
			// autoSave의 값이 true이면 autoSave( )를 호출
			if(autoSave) autoSave();
		}
	}
	public void autoSave() {
		System.out.println("작업파일이 자동저장되었습니다.");
	}
}

 

더보기

<출력값>

1 2 3 4 5 작업파일이 자동저장되었습니다.
6 7 8 작업파일이 자동저장되었습니다.
9 10 11 작업파일이 자동저장되었습니다.
12 13 14 작업파일이 자동저장되었습니다.
15 프로그램을 종료합니다. 


쓰레드의 실행제어 

- 쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 함

- 효율적인 멀티쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 낭비없이 잘 사용하도록 프로그래밍 해야함

 

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

 

메서드 설명
static void sleep(long millis)
static void sleep(long millis, int nanos)
지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다.(잠들게) 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. ex) 5-> 5000
void join( )
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join( )을 호출한 쓰레드로 다시 돌아와 실행을 계속한다. (다른 쓰레드가 작업하는 것을 기다리기)
void interrupt( ) sleep( )이나 join( )에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 쓰레드에서는 Interrupted Exception이 발생함으로써 일시정지 상태를 벗어나게 된다.(깨우는 것)
void stop( ) 쓰레드를 즉시 종료시킨다.
void suspend( ) 쓰레드를 일시정지 시킨다. resume( )을 호출하면 다시 실행대기상태가 된다.
void resume( ) suspend( )에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.(재개)
static void yield( ) 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.

 

static void sleep(), static void yield( ) : 쓰레드 자기자신에게만 호출가능(다른 쓰레드한테 적용 불가능)

 


쓰레드의 상태

 

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

 

쓰레드의 상태가 변화되는 과정

 

쓰레드의 상태가 변화되는 과정

 

1) 쓰레드를 생성하고 start( )를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 Queue와 같은 구조로 먼저 실행대기 열에 들어온 쓰레드가 먼저 실행된다.
2) 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.
3) 주어진 실행시간이 다되거나 yield( )를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
4) 실행 중에 suspend( ), sleep( ), wait( ), join( ), I/O block에 의해 일시정지상태가 될 수 있다.
5) 지정된 일시정지시간이 다되거나(time-out), notify( ), resume( ), interrupt( )가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
6) 실행을 모두 마치거나 stop( )이 호출되면 쓰레드는 소멸된다.

Sleep( ) - 일정시간동안 쓰레드를 멈추게 한다.

- 현재 쓰레드를 지정된 시간동안 멈추게 한다.

- static 메서드, static 메서드는 자기자신을 호출함(잠잘 시간)

- sleep( )에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptedException 발생, 예외처리 해줘야함), 잠에서 꺠어나 실행되기 상태가 된다.

 

static void sleep(long millis)            // 천분의 일초 단위

static void sleep(long millis, int nanos)  // 천분의 일초 + 나노초

-> 3(3*1000)

 

 

예제)

 

public class Ex13_8 {

	public static void main(String[] args) {
		ThreadEx8_1 th1 = new ThreadEx8_1();  // Thread를 상속받은 ThreadEx8_1객체 생성
		ThreadEx8_2 th2 = new ThreadEx8_2();
		
		th1.start();
		th2.start();
		
		try {
			Thread.sleep(2000);    
		} catch(InterruptedException e) {}
		
		System.out.println("<<main 종료>>");
	} // main
}
class ThreadEx8_1 extends Thread { 
	public void run() {
		for(int i=0; i<100; i++) 
			System.out.print("-");
		System.out.print("<<th1 종료>>");
	}// run()
}
class ThreadEx8_2 extends Thread {
	public void run() {
		for(int i=0; i<100; i++)
			System.out.println("|");
		System.out.println("<<th2 종료>>");
	}// run()
}

 

더보기

<출력값>

---||||||||||||||||||||||||||||||||||-----||||-------|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-------------------------------------------------------------------------------------<<th1 종료>>|<<th2 종료>>
<<main 종료>>

 

-> 예제에서 쓰레드가 3개 있음(main 쓰레드, th1 쓰레드, th2 쓰레드) main 쓰레드에서 sleep( )을 호출하기 때문에 main 쓰레드가 잠을 잠

 

-> try-catch 구문을 안쓰고 이렇게 따로 delay라는 static 메서드를 만들어줘도 가능!(항상 필수예외처리니까)

public static void main(String[] args) {

               ThreadEx8_1 th1 = new ThreadEx8_1();

               ThreadEx8_2 th2 = new ThreadEx8_2();

              

               th1.start();

               th2.start();

              

               delay(2000);

              

               System.out.println("<<main 종료>>");

       } // main

      

       static void delay(long millis) {

               try {

                      Thread.sleep(millis);   

               } catch(InterruptedException e) {}

       }

 

 

 


Interrupt( )와 Interrupted( ) - 쓰레드의 작업을 취소한다.

- 대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.

- 대기상태(작업의 중단) sleep( ), join( ) wait( )

 

 

*관련 메서드* 

 

void interrupt( ) 쓰레드의 interrupted 상태를 false에서 true로 변경(기본은 false으로 초기화 되어있음)
Boolean isInterrupted( ) 쓰레드의 interrupted 상태를 반환
static Boolean interrupted( ) 현재 쓰레드의 interrupted상태를 알려주고, false로 초기화

 

Mythread th = new Mythread();
th.start();

th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.

class Mythread extends Thread {
	public void run() {
		while(!interrupted()) {  // interrupted()의 결과가 false인 동안 반복
	
	}
}

 

 

예제) 카운트다운이 되다가 사용자가 값을 입력하면 interrupt( )에 의해서 멈추고 카운트가 종료되는 프로그램

 

import javax.swing.JOptionPane;

public class ThreadEx13 {

	public static void main(String[] args) {
		ThreadEx13_1 th1 = new ThreadEx13_1();
		th1.start();
		
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
		System.out.println("입력하신 값은 " + input + "입니다.");
		th1.interrupt();  // interrupt()를 호출하면, interrupted상태가 true가 된다. 
		System.out.println("isInterrupted():" + th1.isInterrupted()); // true
	}
}
class ThreadEx13_1 extends Thread {
	public void run() {
		int i = 10;
		
		while(i!=0 && !isInterrupted()) {
			System.out.println(i--);
			try {
				 Thread.sleep(500);
			} catch (InterruptedException e) { 
				interrupt();    //(1)
			}
		}
		System.out.println("카운트가 종료되었습니다.");
	}
}

 

더보기
-> Thread.sleep(500)에서 InterruptedException이 발생했기 때문에  sleep( )에 의해 쓰레드가 잠시 멈춰있을 때, interrupt( )를 호출하면 InterruptedException이 발생되고, 쓰레드의 interrupted상태는 false로 자동 초기화되기 때문에!!!  catch블럭에 interrupt( )를 추가로 넣어줘서 쓰레드의 interrupted의 상태를 true로 다시 바꿔줌!

 


suspend( ), resume( ), stop( )

- 쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다.

 

void suspend( ) 쓰레드를 일시정지 시킨다.
void resume( ) suspend( )에 의해 일시 정지된 쓰레드를 실행대기상태로 만든다.
void stop( ) 쓰레드를 즉시 종료시킨다.

 

-> suspend( )와 stop( )이 교착상태(deadlock)를 일으키기 쉽게 작성되어 있어서 사용 권장 xx

-> 그래서 이 메서드들은 모두  'deprecated' 되었다. 

-> 교착상태: 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태

-> deprecated: 전에는 사용되었지만, 앞으로 사용하지 않을 것을 권장한다. deprecated된 메서드는 하위 호환성을 위해서 삭제하지 않는 것

 


join( ) – 다른 쓰레드의 작업을 기다린다.

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

- join( )sleep( )처럼 interrupt( )에 의해 대기상태에서 벗어날 수 있고, join( )이 호출되는 부분을 try-catch문으로 감싸야 한다.

-> sleep( )과 유사하지만 sleep( ) static 메서드이고, join( )현재 쓰레드가 아닌 특정 쓰레드에 대해 동작함

 

void join( ) 작업이 모두 끝날 때까지
void join(long millis) 천분의 일초 동안
void joint(long millis, int nanos) 천분의 일초 + 나노초 동안

 

-> 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야 할 필요가 있을 때 join을 사용

 

예제) 

 

public class ThreadEx19 {
	static long startTime = 0;
	public static void main(String[] args) {
		ThreadEx19_1 th1 = new ThreadEx19_1();
		ThreadEx19_2 th2 = new ThreadEx19_2();
		
		th1.start();
		th2.start();
		startTime = System.currentTimeMillis();
		
		try {
			th1.join();  // main쓰레드가 th1의 작업이 끝날때까지 기다린다.
			th2.join();  // main쓰레드가 th2의 작업이 끝날때까지 기다린다.
		} catch (InterruptedException e) { }
		
		System.out.println("소요시간: " +  (System.currentTimeMillis()-ThreadEx19.startTime));
	}
}
class ThreadEx19_1 extends Thread { 
	public void run() {
		for(int i=0; i<100; i++) {
			System.out.print(new String("-"));
		}
	}//run( )
}
class ThreadEx19_2 extends Thread {
	public void run() {
		for(int i=0; i<100; i++) {
			System.out.print(new String("|"));
		}
	}//run( )
}

 

더보기

<출력값>

-----------------------------------------------------|||-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----------------------------------------------소요시간: 41 -----------------------------------------------------|||-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----------------------------------------------소요시간: 41

 

-> join( )을 사용해서 main쓰레드가 쓰레드 th1th2의 작업을 마칠 때까지 기다리도록 했고, 소요시간: 이 제일 마지막에 출력됐다. 만약 주석으로 try-catch구문을 없애게 되면 main메서드가 종료되면서 소요시간은 0이된다.

 


yield( ) – 다른 쓰레드에게 양보한다.

- 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기한다. (static 메서드)

- yield( )interrupt( )를 적절히 사용하면, 응답성과 효율을 높일 수 있다.

- OS 스케줄러에게 통보하는 것이라서 정확하게 시간이 나눠지지는 않는다.

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

 

 

https://everysmallstep.tistory.com/106

 

자바의 정석10-1 프로세스와 쓰레드

프로세스와 쓰레드(process & thread) - 프로세스: 실행 중인 프로그램, 자원(resources데이터와 메모리)과 쓰레드로 구성 - 쓰레드: 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의

everysmallstep.tistory.com