코린이의 소소한 공부노트

데몬 쓰레드와 쓰레드의 실행제어, 상태 본문

Java

데몬 쓰레드와 쓰레드의 실행제어, 상태

무지맘 2022. 12. 9. 17:07

[데몬 쓰레드]

boolean isDaemon()
// 쓰레드 그룹이 데몬 쓰레드 그룹이면 true를 반환한다.

void setDaemon(boolean on)
// 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경한다. on을 true로 지정하면 데몬 쓰레드가 된다.
// setDaemon()은 반드시 start()를 호출하기 전에 실행되어야 한다.
// start() 후에는 데몬 쓰레드로 변경할 수 없기 때문이다.
// start() 전에 setDaemon()을 호출하면 IllegalThreadStateException이 발생한다.

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

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

3. 가비지 컬렉터(garbage collector), 자동저장, 화면 자동갱신 등에 사용된다.

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

// 데몬 쓰레드를 이용한 자동저장 예시
class DTEx implements Runnable {
    static boolean autoSave = false;
    
    public static void main(String[] args) { // main(일반) 쓰레드
        Thread t = new Thread(new DTEx());
        t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
        t.start();
        for(int i=1; i <= 10; i++) {
            try{
                Thread.sleep(1000);
            } catch(InterruptedException e) {}
            System.out.print(i + " ");
            if(i==5) autoSave = true; // 5초 후부터 자동저장 시작
        } // for
        System.out.println("프로그램을 종료합니다.");
    } // main()
    
    public void run() { // 데몬 쓰레드
        while(true) { // 무한루프지만 일반 쓰레드가 모두 종료되면 자동종료되기 때문에 괜찮다.
            try { 
                Thread.sleep(3 * 1000);
            } catch(InterruptedException e) {}
           if(autoSave) autoSave(); // autoSave의 값이 true이면 3초마다 autoSave()를 호출한다.
        }
    }
    
    public void autoSave() {
        System.out.println("작업파일이 자동저장되었습니다.");
    }
} // end of class

// 위 코드를 실행시킨 결과
1 2 3 4 5 작업파일이 자동저장되었습니다.
6 7 8 작업파일이 자동저장되었습니다.
9 10 프로그램을 종료합니다.
// t.setDaemon(true);가 없을 때의 실행 결과
1 2 3 4 5 작업파일이 자동저장되었습니다.
6 7 8 작업파일이 자동저장되었습니다.
9 10 프로그램을 종료합니다.	// main은 종료되었지만
작업파일이 자동저장되었습니다.
작업파일이 자동저장되었습니다.	// 데몬 쓰레드는 무한루프를 돌고 있다.
...

 

[쓰레드의 상태]

1. NEW

  - 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태

2. RUNNABLE

  - 실행 중 또는 실행 가능한 상태

3. BLOCKED

  - 동기화 블록에 의해 일시 정지된 상태

  - lock이 풀릴 때까지 기다림

4. WAITING, TIMED_WAITING

  - 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태

  - TIMED_WAITING은 일시정지 시간이 지정된 경우

5. TERMINATED

  - 쓰레드의 작업이 종료된 상태

 

[쓰레드의 메서드]

1. 잠자기, 다른 쓰레드 기다리기 <-> 깨우기, 방해하기

1) sleep()

static void sleep(long millis)
static void sleep(long millis, int nanos)
// 지정된 시간동안 쓰레드를 일시정지 시킨다.
// millis는 천 분의 일초 단위, nanos는 10^(-9)초 단위
// 지정된 시간이 지나면(time-out) 자동적으로 다시 실행대기 상태가 된다.
// interrupt()로 깨울 수 있기 때문에 예외 처리가 필수적이다.
// sleep()은 static 메서드이기 때문에 자기 자신에게만 호출할 수 있다.

// 예시
try{
    Thread.sleep(1, 500000); // 쓰레드를 0.0015초 동안 멈추게 한다.
} catch(InterruptedException e) { } // Exception의 자손이므로 checked error
// 예외처리를 매번 하는 것이 다소 귀찮으므로 따로 메서드를 만들어놓기도 한다.
static void delay(long millis){
    try{
        Thread.sleep(millis);
    } catch(InterruptedException e) { }
}
...
delay(1000);

2) join()

void join()
void join(long millis)
void join(long millis, int nanos)
// 지정된 시간동안 쓰레드가 실행되로록 한다.
// 지정된 시간이 지나거나 작업이 종료(시간지정 없을 때)되면
// join()을 호출한 쓰레드로 다시 돌아와 실행을 계속 한다.
// sleep()과 마찬가지로 예외처리가 필수다.

// 예시
ThreadEx t1 = new ThreadEx();
ThreadEx t2 = new ThreadEx();
t1.start();
t2.start();
try{
    t1.join(); // main쓰레드가 t1의 작업이 종료될 때까지 기다린다.
    t2.join(); // main쓰레드가 t2의 작업이 종료될 때까지 기다린다.
} catch(InterruptedException e) { } // e가 발생하면 기다림을 멈추고 main의 일을 재개한다.

3) interrupt()

void interrupt()
// sleep()이나 join()에 의해 일시정지 상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.
// 쓰레드의 interrupted 상태를 false에서 true로 전환한다.
// 해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지 상태를 벗어난다.

boolean isInterrupted()
// 쓰레드의 interrupted 상태를 반환한다.

static boolean interrupted()
// 현재 쓰레드의 interrupted 상태를 반환하고, false로 초기화한다.
// static 메서드이기 때문에 자기 자신에게만 쓸 수 있다.

// 예시
// main()
ThreadEx t1 = new ThreadEx();
t1.start();
t1.interrupt();

// ThreadEx의 run()
System.out.println(this.isInterrupted()); // true
System.out.println(this.isInterrupted()); // true
System.out.println(Thread.interrupted()); // true
System.out.println(Thread.interrupted()); // false
// isInterrupted()는 상태를 바꾸지 않기 때문에
// 다시 호출해도 true가 출력되지만
// static 메서드인 interrupted()는 쓰레드의 상태를 false로 초기화해서
// 다시 호출하면 false가 출력된다.

2. 일시정지 <-> 재개 // 종료 - 3개의 메서드는 deprecated: 교착상태(dead-lock) 발생 가능성 높음

void suspend()
// 쓰레드를 일시정지 시킨다.

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

void stop()
// 쓰레드를 즉시 종료시킨다.

// 어떻게 동작하는지 쉽게 구현해놓은 코드
// Thread를 구현해놓은 클래스 내부
boolean suspended = false;
boolean stooped = false;
public void suspend() { suspended = true; }
public void resume() { suspended = false; }
public void stop() { stopped = true; }

public void run(){
    while(!stopped){ // stop()이 호출되지 않는 동안 계속 while문을 수행한다.
        if(!suspended){ // suspend()가 호출되면 resume()가 호출되기 전까지 if문을 실행하지 않는다.
            // 메서드 실행 내용
        }
    }
}

3. 다른 쓰레드에게 실행 양보하기

static void yield()
// 실행 중에 다른 쓰레드에게 양보하고 실행대기 상태가 된다.
// 남은 시간을 실행 대기 상태의 맨 앞 쓰레드에게 양보하고
// 자신은 실행 대기 상태의 가장 뒤로 간다.
// static 메서드이기 때문에 자기 자신에게만 호출할 수 있다.

// 예시 - 위에 있던 코드
public void run(){
    while(!stopped){
        if(!suspended){ // 일시정시 상태가 되면 while문만 계속 돌게 된다.(busy-waiting)
            // 메서드 실행 내용
        } else{
            Thread.yield(); // 이를 방지하기 위해 else문에 yield()를 쓸 수 있다.
        }
    }
}