Thread의 상태

de_sj_awa·2021년 4월 12일
0
post-custom-banner

1. 쓰레드란?

자바의 메인 메소드 역시 하나의 실행흐름으로서, 메인 쓰레드에 해당한다. 이것은 main() 메소드에서 Thread.currentThread().getName();을 통해 확인이 가능하다. 쓰레드를 이용하면 하나의 프로세스에서도 병렬적(동시적)으로 처리, 즉 여러 개의 처리 루틴을 가질 수 있다. 쓰레드는 특히 짧은 시간 내에 많은 사용자를 처리해야 하는 대용량 시스템이나 CPU 활동을 극대로 해서 처리해야 하는 복잡한 연산 작업 등에 많이 쓴다.

2. 쓰레드 객체 생성 및 사용 방법

작업 Thread의 생성

  1. thread code를 작성
  2. JVM에서 thread 객체 생성
  3. thread code를 실행하도록 요청

Thread code란?

run() method에 작성된 code를 thread code라고 말한다.
thread는 run() method의 실행과 종료를 기준으로 thread도 종료되기 때문이다.
따라서 thread code를 작성하기 위해서는 run() method를 오버라이딩 해야한다.

Thread 객체 생성 방법 2가지

  1. extends Thread를 상속받아 run()을 오버라이딩
    • 객체 하나에 쓰레드 하나를 연결시킬 때 이렇게 쓴다.
      1) Thread class를 상속받아 sub class 생성
      2) Thread class의 run() method를 overriding (thread code 작성)
      3) 쓰레드 객체 생성
      4) start() method를 호출하여 작업 thread 시작
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.company;
 
class Task_Thread extends Thread{   //1. Thread class를 상속받아 sub class 생성
 
    @Override
    public void run(){              //2. run() method를 Overriding 해서 thread code 생성
        while(true){
            System.out.println("New Task Thread!");
            try{
                currentThread().sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class Main {
 
    public static void main(String[] args) {
        System.out.println("Main Task Start");
        Task_Thread task = new Task_Thread();   // 3. Thread 객체 생성
        task.start();                                       // 4. 작업 thread 시작
    }
}
 
cs
  1. implements Runnable을 이용해서 run()을 오버라이딩
    • 보통 다중 상속 문제로 인해 이 방식을 많이 쓴다.
      1) Runnable interface를 implements해 구현 c 생성
      2) Runnable interface의 run() method를 Overriding (Thread code 작성)
      3) thread 객체 생성
      4) start() 메소드를 호출하여 thread 시작
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.company;
 
import javax.swing.text.html.parser.DTDConstants;
 
class Task2_Thread implements Runnable {    //1. Runnable interface를 구현한 class 생성
 
    @Override
    public void run() {                     //2. run() method를 Overriding해서 thrad code 생성
        while(true){
            System.out.println("New Task Thread2!");
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class Main {
 
    public static void main(String[] args) {
        System.out.println("Main Task start");
        Thread task = new Thread(new Task2_Thread());    //3. thread 객체 생성
        task.start();                                    //4. 작업 thread 시작
 
    }
}
 
cs

3. 쓰레드의 생명주기

4. 쓰레드의 정보

쓰레드들이 내부에 갖고 있는 정보들은 아래와 같다. 이 중 일부는 getter를 통해 보거나, setter를 통해 설정할 수 있다.

타입 관련메소드 내용
name String get, set 쓰레드 이름
id int get 쓰레드 고유 식별자 번호
PC(Program Count) int 현재 실행중인 쓰레드 코드의 주소
State int get 6개 중 하나
Priority int get, set 쓰레드 스케줄링 때 사용되는 값으로 1~10(10이 최상위, 기본 5)
ThreadGroup int get 여러 개의 쓰레드가 하나의 그룹을 형성할 수 있는데, 쓰레드가 속한 그룹
Register Stack 쓰레드가 실행되는 동안 레지스터들의 값

5. 쓰레드의 상태

쓰레드의 상태를 제어해야 할 필요가 있을 때, 제어를 하기 전에 해당 쓰레드의 상태를 알아야 한다. 이 때 getState() 메서드를 사용하여 상태를 확인한다. 상태는 크게 6가지 중 하나이다.

getState()의 쓰레드 상태에 따른 Thread.State 열거 상수

상태 열거 상수 설명
객체 생성 NEW 쓰레드 객체가 생성, 아직 start() 메소드가 호출되지 않은 상태
실행 대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
WAITING 다른 쓰레드가 통지할 때까지 기다리는 상태
일시 정지 TIME_WAITING 주어진 시간 동안 기다리는 상태
BLOCKED 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태
종료 TERMINATED 실행을 마친 상태

5. 쓰레드의 상태 제어

쓰레드는 start() 하게 되면 일반적으로 다음과 같은 상태로 진행된다.

  • 경우에 따라서 쓰레드는 실행 상태에서 실행 대기 상태로 가지 않을 수도 있다.
  • 실행 상태에서 일시 정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태이다.
  • 쓰레드가 다시 실행 상태로 가기 위해서는 일시 정지 상태에서 실행 대기 상태로 가야 한다.

  1. 프로그래밍을 통한 쓰레드 상태 확인

이 예제는 쓰레드의 상태를 출력해주며, 생성자 매개값으로 받은 타겟 쓰레드의 상태를 0.5초 주기로 출력해주는 클래스이다.

StatePrintThread.java : 타겟 쓰레드의 상태를 출력하는 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package thread_state;
 
public class StatePrintThread extends Thread{
 
    private Thread targetThread;
 
    public StatePrintThread(Thread targetThread){
        this.targetThread = targetThread;
    }
 
    @Override
    public void run() {
        while(true){
            Thread.State state = targetThread.getState();   //쓰레드 상태 얻기
            System.out.println("타겟 쓰레드 상태 : " + state);
 
            // 객체 생성 상태일 경우 실행 대기 상태로 만듦
            if(state == Thread.State.NEW){
                targetThread.start();
            }
 
            // 종료 상태일 경우 while문을 종료함
            if(state == Thread.State.TERMINATED){
                break;
            }
            try{
                Thread.sleep(500);
            }catch (InterruptedException e){}
        }
    }
}
 
cs

TargetThread.java : 10억 번 루프를 돌아 RUNNABLE 상태를 유지하고, sleep() 메소드를 호출해서 1.5초간 TIMED_WAITING 상태를 유지한다. 또 다시 10억 번 루프를 돌아 RUNNABLE 상태를 유지한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread_state;
 
public class TargetThread extends Thread{
    
    @Override
    public void run() {
        for(long i = 0; i < 1000000000; i++){}
        
        try{
            Thread.sleep(1500);
        }catch(Exception e){}
        
        for(long i = 0; i < 1000000000; i++){}
    }
}
 
 
cs

TargetThread가 객체로 생성되면 NEW 상태를 가지고, run() 메소드가 종료되면 TERMINATED 상태로 변한다.

NEW -> RUNNABLE -> TIME_WAITING -> RUNNABLE -> TERMINATED

ThreadStateExample.java : StatePrintThread를 생성해서 매개값으로 전달받은 TargetThread의 상태를 출력하는 클래스

1
2
3
4
5
6
7
8
9
10
package thread_state;
 
public class ThreadStateExample {
 
    public static void main(String[] args){
        StatePrintThread statePrintThread = new StatePrintThread(new TargetThread());
        statePrintThread.start();
    }
}
 
cs

실행을 하면

타겟 쓰레드 상태 : NEW
타겟 쓰레드 상태 : TIMED_WAITING
타겟 쓰레드 상태 : TIMED_WAITING
타겟 쓰레드 상태 : TIMED_WAITING
타겟 쓰레드 상태 : RUNNABLE
타겟 쓰레드 상태 : TERMINATED

과 같은 결과가 나온다.

  1. 프로그래밍을 통한 쓰레드 상태 제어

suspend(), resume(), stop() 이 3가지 메서드는 쓰레드의 안정성을 해친다고 여겨져 사용하지 않도록 권장된 Deprecated 메소드 이다.

쓰레드 상테 제어와 관련된 메소드

메소드 설명
interrupt() 일시 정지 상태의 쓰레드에서 InterruptedException 예외를 발생시켜, 예외처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다.
notify()
notifyAll()
동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 쓰레드를 실행 대기 상태로 만든다.
resume() suspend(0 메소드에 의해 일시 정지 상태에 있는 쓰레드를 실행 대기 상태로 만든다.
- Deprecated(대신 notify(), notifyAll() 사용)
sleep(long millis)
sleep(long millis, int nanos)
주어진 시간 동안 쓰레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다.
join()
join(long millis)
join(long millis, int nanos)
join() 메소드를 호출한 쓰레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 쓰레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다.
wait()
wait(long millis)
wait(long millis, int nanos)
동기화(synchronized) 블록 내에서 쓰레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다.
suspend() 쓰레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다.
- Deprecated(대신 wait() 사용)
yield() 실행 중에 우선순위가 동일한 다른 쓰레드에게 실행을 양보하고 실행 대기 상태가 된다.
stop() 쓰레드를 즉시 종료시킨다. - Deprecated

위 표에서 wait(), notify(), notifyAll()은 Object 클래스의 메소드이고, 그 이외의 메소드는 Thread 클래스의 메소드이다.

1) sleep()

sleep() : 주어진 시간 동안 일시 정지

  • Thread.sleep()를 호출한 쓰레드는 주어진 시간 동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아간다.
  • 매개값에는 얼마 동안 일시 정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 시간을 준다. ex) Thread.sleep(3000); -> 3초
  • 이일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException이 발생하기 때문에 예외 처리가 필요하다.

SleepExample.java - 3초 주기로 10번 비프음 발생

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_state_control;
 
import java.awt.Toolkit;
 
public class SleepExample {
    public static void main(String[] args){
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for(int i = 0; i < 10; i++){
            toolkit.beep();
            try{
                Thread.sleep(3000); //3초동안 메인 스레드를 일시 정지 상태로 만듦
            }catch(InterruptedException e){}
        }
    }
}
 
cs

2. yield()

yield() : 다른 쓰레드에게 실행 양보

  • yield()는 실행되고 있는 쓰레드 주체가 혼자만 작동하지 않고, 다른 쓰레드가 실행 기회를 가질 수 있도록 해준다.
  • 아래 그림은 yield()로 인한 쓰레드의 상태 변화이다.

YieldExample.java - 쓰레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package thread_state_control;
 
public class YieldExample {
 
    public static void main(String[] args){
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();
        //ThreadA, ThreadB 모두 실행
        threadA.start();
        threadB.start();
 
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){}
 
        threadA.work = false;   //ThreadB만 실행
 
        try{
            Thread.sleep(3000);
        }catch(InterruptedException e){}
 
        threadA.work = true;    //ThreadA, ThreadB 모두 실행
 
        try{
            Thread.sleep(3000);
        }catch(InterruptedException e){}
 
        // ThreadA, ThreadB 모두 종료
        threadA.stop = true;
        threadB.stop = true;
    }
}
 
cs

ThreadA.java - 쓰레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package thread_state_control;
 
public class ThreadA extends Thread{
 
    public boolean stop = false;
    public boolean work = true;
 
 
    @Override
    public void run() {
        while(!stop){     // stop이 true가 되면 while문 종료
            if(work){
                System.out.println("ThreadA 작업 내용");
            }else{
                Thread.yield(); // work가 false가 되면 다른 쓰레드에게 실행 양보
            }
        }
        System.out.println("ThreadA 종료");
    }
}
 
 
cs

ThreadB.java - 쓰레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package thread_state_control;
 
public class ThreadB extends Thread{
 
    public boolean stop = false;
    public boolean work = true;
 
 
    @Override
    public void run() {
        while(!stop){     // stop이 true가 되면 while문 종료
            if(work){
                System.out.println("ThreadB 작업 내용");
            }else{
                Thread.yield(); // work가 false가 되면 다른 쓰레드에게 실행 양보
            }
        }
        System.out.println("ThreadB 종료");
    }
}
 
cs

실행을 하게 되면 ThreadA와 ThreadB가 번갈아가며 실행되다가 yield()로 인하여 ThreadB가 더 많은 실행 기회를 가지게 되어 ThreadB만 실행이 되는 상황이 나온다. 그러다 다시 ThreadA와 ThreadB가 번갈아가며 실행이 되면서 결국 마지막에 모든 쓰레드가 종료된다.

3. join()

join() : 다른 쓰레드의 종료를 기다림

  • 쓰레드는 다른 쓰레드와 독립적으로 실행하는 것이 기본이다.
  • 하지만 다른 쓰레드가 종료될 때까지 기다렸다가 실행해야 하는 경우가 발생할 수 있다.
  • 아래 그림은 join()을 사용했을 때의 쓰레드 상태를 나타낸 것이다.

SumThread.java - 1부터 100까지 합을 계산하는 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package thread_state_control;
 
public class SumThread extends Thread{
 
    private long sum;
 
    public long getSum(){
        return sum;
    }
 
    public void setSum(long sum){
        this.sum = sum;
    }
 
    @Override
    public void run() {
        for(int i = 1; i <= 100; i++){
            sum += i;
        }
    }
}
cs

JoinExample.java - 다른 쓰레드가 종료될 때까지 일시 정지 상태 유지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_state_control;
 
public class JoinExample {
 
    public static void main(String[] args){
        SumThread sumThread = new SumThread();
        sumThread.start();
 
        try{
            sumThread.join();   //sumThread가 종료할 때까지 main 쓰레드를 일시 정지시킴
        }catch(InterruptedException e){}
 
        System.out.println("1~100 합 : " + sumThread.getSum());
    }
}
 
cs

join() 예제를 실행해보면 정상적으로 5050이 출력된다.
하지만 JoinExample의 9 ~ 11행인 try ~ catch 구문을 주석처리하고 실행하면 0이 나오게 된다.
이러한 이유는 SumThread가 계산 작업을 완료하지 않은 상태에서 합을 먼저 출력하기 때문이다.

4. 쓰레드 간 협업에 대한 메서드 wait(), notify(), notifyAll()

상황에 따라서 두 개의 쓰레드를 교대로 번갈아가며 실행해야 하는 경우가 있다. 정확한 교대 작업이 필요한 경우, 자신의 작업이 끝나면 상대방 쓰레드를 일시 정지 상태에서 풀어주고 자신은 일시 정지 상태로 만든다.
이 방법의 핵심은 공유 객체에 있다.

공유 객체는 두 쓰레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓는다. 한 쓰레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 쓰레드를 실행 대기 상태로 만들고, 자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만든다.

wait() 대신에 wait(long timeout)이나, wait(long timeout, int nanos)를 사용하면 notify()를 호출하지 않아도 지정된 시간이 지나면 쓰레드가 자동적으로 실행 대기 상태가 된다.

notify()와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify()는 wait()에 의해 일시 정지된 쓰레드 중 한 개를 실행 대기 상태로 만든다. notifyAll() 메소드는 wait()에 의해 일시 정지된 모든 쓰레드들을 실행 대기 상태로 만든다.

wait(), notify(), notifyAll() 메소드는 Thread 클래스가 아닌 Object 클래스에 선언된 메소드이므로 모든 공유 객체에서 호출이 가능하다. 주의할 점은 이 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다.

WorkObject.java - 두 쓰레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread_state_control2;
 
public class WorkObject {
    
    public synchronized void methodA(){
        System.out.println("ThreadA의 methodA() 작업 실행");
        notify();   // 일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 만듦
        try{
            wait(); // ThreadA를 일시 정지 상태로 만듦
        }catch (InterruptedException e){}
    }
 
    public synchronized void methodB(){
        System.out.println("ThreadB의 methodB() 작업 실행");
        notify();   // 일시 정지 상태에 있는 ThreadA를 실행 대기 상태로 만듦
        try{
            wait(); // ThreadB를 일시 정지 상태로 만듦
        }catch(InterruptedException e){}
    }
}
cs

ThreadA.java - WorkObject의 methodA()를 실행하는 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread_state_control2;
 
public class ThreadA extends Thread{
 
    private WorkObject workObject;
 
    public ThreadA(WorkObject workObject){
        this.workObject = workObject;   // 공유 객체를 매개로 받아 필드에 저장
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            workObject.methodA();   // 공유 객체의 methodA()를 10번 반복
        }
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}
    }
}
cs

ThreadB.java - WorkObject의 methodB()를 실행하는 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread_state_control2;
 
public class ThreadB extends Thread{
 
    private WorkObject workObject;
 
    public ThreadB(WorkObject workObject){
        this.workObject = workObject;   // 공유 객체를 매개로 받아 필드에 저장
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            workObject.methodB();   // 공유 객체의 methodB()를 10번 반복
        }
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}
    }
}
cs

WaitNotifyExample.java - 두 쓰레드를 생성하고 실행하는 메인 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread_state_control2;
 
 
public class WaitNotifyExample {
 
    public static void main(String[] args){
        WorkObject sharedObject = new WorkObject(); // 공유 객체 생성
 
        // ThreadA와 ThreadB 생성
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
 
        // ThreadA와 ThreadB 실행
        threadA.start();
        threadB.start();
    }
}
cs

예제를 실행해보면 다음과 같이

ThreadA의 methodA() 작업 실행
ThreadB의 methodB() 작업 실행

ThreadA와 ThreadB를 10번씩 실행하는 것을 확인할 수 있다.
interrupt()가 발생하면 각각의 작업 쓰레드(ThreadA, ThreadB)에서는 try 구문에 있는 Thread.sleep()을 실행하게 된다.


생산자, 소비자 쓰레드

위 그림을 통해 사용할 예제는 다음과 같다.

  • 데이터를 저장하는 쓰레드(생산자 쓰레드)가 데이터를 저장(생산)한다.
  • 데이터를 소비하는 쓰레드(소비자 쓰레드)가 데이터를 읽고 처리한다.

이 2가지를 처리하는 교대 작업을 구현한 예제이다.

  • 생성자 쓰레드는 소비자 쓰레드가 읽기 전에 새로운 데이터를 두 번 생성하면 안된다. (setData() 메소드 두 번 실행 X)
  • 소비자 쓰레드는 생성자 쓰레드가 새로운 데이터를 생성하기 전에 이전 데이터를 두 번 읽어서도 안된다. (getData() 메소드 두 번 실행 X)

DataBox.java - 두 쓰레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package thread_state_control3;
 
public class DataBox {
 
    private String data;
 
    public synchronized String getData() {
        // data 필드가 null이면 소비자 쓰레드를 일시 정지 상태로 만듦
        if (this.data == null) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        String returnValue = data;
        System.out.println("ConsummerThread가 읽은 데이터 : " + returnValue);
 
        // data 필드를 null로 만들고 생산자 쓰레드를 실행 대기 상태로 만듦
        data = null;
        notify();
        return returnValue;
    }
    
    public synchronized void setData(String data){
        // data 필드가 null이 아니면 생산자 쓰레드를 일시 정지 상태로 만듦
        if(this.data != null){
            try{
                wait();
            }catch (InterruptedException e){}
        }
        this.data = data;
        System.out.println("ProducerThread가 생성한 데이터 : " + data);
        notify();
    }
}
cs

ProducerThread.java - 데이터를 생산(저장)하는 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package thread_state_control3;
 
public class ProducerThread extends Thread{
 
    private DataBox dataBox;
 
    public ProducerThread(DataBox dataBox){
        this.dataBox = dataBox; // 공유 객체를 필드에 저장
    }
 
    @Override
    public void run() {
        for(int i = 1; i <= 3; i++){
            String data = "Data-" + i;
            dataBox.setData(data);  //새로운 데이터를 저장
        }
    }
}
 
cs

ConsumerThread.java - 데이터를 소비하는(읽는) 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package thread_state_control3;
 
public class ConsumerThread extends Thread{
    
    private DataBox dataBox;
    
    public ConsumerThread(DataBox dataBox){
        this.dataBox = dataBox;     // 공유 객체를 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i = 1; i <= 3; i++){
            String data = dataBox.getData();    // 새로운 데이터를 읽음
        }
    }
}
 
cs

WaitNotifyExample.java - 두 쓰레드를 생성하고 실행하는 메인 쓰레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package thread_state_control3;
 
public class WaitNotifyExample {
 
    public static void main(String[] args){
        DataBox dataBox = new DataBox();
 
        ProducerThread producerThread = new ProducerThread(dataBox);
        ConsumerThread consumerThread = new ConsumerThread(dataBox);
 
        producerThread.start();
        consumerThread.start();
    }
}
 
cs

5. 정지와 관련된 쓰레드의 완전한 종료(stop 플래그, interrupt())

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

stop() 메소드를 사용하면 쓰레드를 즉시 종료시킬 수 있지만, 이 메소드는 deprecated 메소드이다. 그 이유는 stop() 메소드로 쓰레드를 갑자기 종료하게 되면 쓰레드가 사용 중이던 자원들이 불안전한 상태로 남겨지기 때문이다.

stop() 대신 stop 플래그를 이용하는 방법과 interrupt() 메소드를 이용하는 방법이 있다.

1) stop 플래그를 이용하는 방법

private boolean stop;

public void run(){
	while(!stop){
    	//쓰레드가 반복 실행하는 코드;
	}
    //쓰레드가 사용한 자원 정리
}

이처럼 stop이 true가 되면 while이 종료된다.
while을 빠져나오게 되면 쓰레드가 사용한 자원을 정리하게 되고, run() 메소드가 끝나게 되면서 쓰레드는 안전하게 종료된다.

stop 플래그를 사용하는 예제

StopFlagExample.java - 1초 후 출력 쓰레드를 중지시킴

2) interrupt() 메소드를 이용하는 방법

  • interrupt() 메소드는 쓰레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 한다.

ThreadA가 ThreadB의 interrupt() 메소드를 실행하게 되면 ThreadB가
sleep() 메소드로 일시 정지 상태가 될 때 ThreadB에서 InterruptedException이 발생하여 예외 처리(catch) 블록으로 이동한다.

결과적으로 ThreadB는 while문을 빠져나와 run() 메소드를 정상 종료하게 된다.

InterruptExample - 1초 후 출력 쓰레드를 중지시킴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_state_control3;
 
public class InterruptExample {
 
    public static void main(String[] args){
        Thread thread = new PrintThread2();
        thread.start();
 
        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){}
 
        thread.interrupt(); // 쓰레드를 종료시키기 위해 InterruptedException을 발생시킴
    }
}
 
cs

PrintThread2.java - 무한 반복해서 출력하는 메소드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_state_control3;
 
public class PrintThread2 extends Thread{
 
    @Override
    public void run() {
        try{
            System.out.println("실행 중");
            Thread.sleep(1);    // InterruptedException 발생
        }catch (InterruptedException e){}
 
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}
 
cs

쓰레드가 실행 대기 또는 실행 상태에 있을 때 interrupt() 메소드가 실행되면 즉시 InterruptedException 예외가 발생하는 것이 아니다. 쓰레드가 미래에 일시 정지 상태가 되면 InterruptedException 예외가 발생한다. 그러므로 쓰레드가 일시 정지 상태가 되지 않으면 interrupt() 메소드 호출은 의미가 없다.

일시 정지를 하지 않고도 interrupt() 호출 여부를 알 수 있는 방법이 있다.
interrupt() 메소드가 호출되었다면 쓰레드의 interrupted()와 isinterrupted() 메소드는 true를 리턴한다.

PrintThread3.java - interrupted()를 stop 플래그처럼 활용하는 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread_state_control3;
 
public class PrintThread3 extends Thread{
 
    @Override
    public void run() {
        while(true){
            System.out.println("실행 중");
            if(Thread.interrupted()){
                break;
            }
        }
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}
 
cs

참고

profile
이것저것 관심많은 개발자.
post-custom-banner

0개의 댓글