스레드 제어와 생명 주기2
1. 인터럽트 - 플래그 변수 사용
특정 스레드 작업을 중간에 중단하려면 어떻게 해야할까?
플래그를 사용해 작업을 중단시켜 보자.
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class ThreadStopMainV1 {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(4000);
log("작업 중단 지시 runFlag=false");
task.runFlag = false;
}
static class MyTask implements Runnable {
volatile boolean runFlag = true;
@Override
public void run() {
while (runFlag) {
log("작업 중");
sleep(3000);
}
log("자원 정리");
log("자원 종료");
}
}
}문제점
위 코드는 기능적으로는 문제가 없지만, task.runFlag = false; 코드 이후에 즉각적으로 work 스레드가 반응하지 않는다.
그 이유는 이미 while 문 안에서 약 3초간 작업을 진행하고 있기 때문에, 작업이 완료된 후(3초가 지난 후) 플래그를 확인할 수 있기 때문이다.
2. 인터럽트 - 인터럽트 발생
인터럽트를 사용하면 WAITING, TIME_WAITING 같은 대기 상태의 스레드를 직접 깨워서, 작동하는 RUNNABLE 상태로 만들 수 있다. (BLOCKED 상태는 인터럽트에 반응하지 않는다..)
아래 코드를 살펴보자.
메인 쓰레드에서 MyTask 인터럽트 발생 시, TIME_WAITING 상태로 있던 해당 스레드가 RUNNABLE 상태로 변경되어 즉각적으로 코드가 실행되는 것을 확인할 수 있다.
인터럽트가 발생하면 해당 스레드에 InterruptedException 이 발생한다.
참고로,
interrup()를 호출했다고 해서 즉시InterruptedException을 던지는 것은 아니다.오직
sleep()처럼 내부에서InterrupException을 던질 때 예외가 발생한다.
이때 2가지 일이 발생한다.
work스레드는TIMED_WAITING상태에서RUNNABLE상태로 변경되고,InterruptedException예외를 처리하면서 반복문을 탈출한다.work스레드의 인터럽트 상태는false로 변경된다.


3. 인터럽트 - 이전 코드 개선1
그런데 앞선 코드에서는 아쉬운 부분이 있다.
아래 코드에서 인터럽트를 체크하지 않기 때문이다.
만약
sleep()메서드가 없었으면, 인터럽트를 확인하지 못할것이다.
while 문 조건에 인터럽트 상태를 확인한다면 조금 더 빨리 반응할 수 있을 것이다.
(물론, 지금 예제에서는 큰 의미가 없다)
아래 코드를 살펴보자
아래 코드는 while 문 조건에 isInterupted() 사용해 실행에는 큰 문제는 없어 보인다.
하지만 큰 문제가 있는데, 바로 work 스레드의 인터럽트 상태가 true 로 유지된다는 점이다.
앞서
sleep()에서InterruptException가 발생한 경우 스레드의 인터럽트 상태는false가 된다.반면에
isInterrupted()메서드는 인터럽트 상태를 바꾸지 않는다. 단순하게 인터럽트의 상태를 확인한다.
아래 코드와 같이 인터럽트 발생 후 자원을 정리해야 하는 과정이 있다면 자원을 정리하지 못하는 상황이 발생한다.
자바에서 인터럽트 예외가 발생하면, 스레드의 인터럽트 상태를 다시 정상(
false) 으로 돌리는 이유가 이런 이유 때문이다.스레드의 인터럽트 상태를 정상으로 돌리지 않으면 이후에도 계속 인터럽트가 발생하게 된다.


4. 인터럽트 - 이전 코드 개선2
Thread.interrupted() 코드를 사용하게 되면 단순히 인터럽트의 상태를 추가하는 것에 추가적으로 인터럽트 상태를 정상(false) 으로 돌려준다.
아래 코드를 살펴보자.
Thread.interrupted() 메서드 사용을 통해서 인터럽트 상태 체크 및 인터럽트 상태를 정상(false) 으로 변경하였기 때문에, 자원 정리가 문제 없이 실행된다.

5. yield - 양보하기
어떤 스레드를 얼마나 실행할지는 운영체제가 스케줄링을 통해 결정한다.
그런데 특정 스레드가 크게 바쁘지 않은 상황 이러서 다른 스레드에 CPU 실행 기회를 양보하고 싶을 수 있다.
이렇게 양보하면, 스케줄링 큐에 대기 중인 다른 스레드가 CPU 실행 기회를 더 빨리 얻을 수 있다.
아래 코드를 살펴보자.
여기서는 3가지 방식을 사용한다.
Empty :
sleep(1),yield()없이 호출한다. 운영체제의 스레드 스케줄링을 따른다.sleep(1): 특정 스레드를 잠시 쉬게한다.RUNNABLE->TIMEWAITING->RUNNABLE(스케줄링 큐)->RUNNABLE
yield(): 다른 스레드에 실행을 양보한다.RUNNABLE->RUNNABLE(스케줄링 큐)->RUNNABLE
Empty
특정 스레드가 쭉 수행된 다음에 다른 스레드가 수행되는 것을 확인할 수 있다.

sleep()
sleep(1) 을 사용해서 스레드의 상태를 1밀리초 동안 아주 잠깐 RUNNABLE -> TIME_WAITING 으로 변경한다.
이렇게 되면 스레드는 CPU 자원을 사용하지 않고, 실행 스케줄링에서 잠깐 제외된다.
1밀리초의 대기 시간 이후 다시 TIMED_WAITING -> RUNNABLE(스케줄링 큐) -> RUNNABLE 상태가 되면서
실행 스케줄링에 포함된다.
하지만, 이 방식은 RUNNABLE -> TIME_WAITING 로 변경되는 복잡한 과정을 거치고, 또 특정 시간만큼 스레드가
실행되지 않는 단점이 있다.
만약, 양보할 스레드가 없다면, 차라리 나의 스레드를 더 실행하는 것이 나은 선택일 수 있다.
(양보할 스레드가 없는데, 나혼자 쉬고있는 꼴이다.. )

yield()
자바 스레드가 RUNNABLE 상태 일 때, 운영체제의 스케줄링은 다음과 같은 상태들을 가질 수 있다.
실행 상태(
RUNNABLE) : 스레드가 CPU 에서 실제로 실행 중이다.실행 대기 상태(
READY == RUNNABLE) : 스레드가 실행될 준비가 되었지만, CPU 가 바빠서 스케줄링 큐에서 대기 중이다.
운영체제는 실행 상태의 스레드들을 잠깐만 실행하고 실행 대기 상태로 만든다. 그리고 실행 대기 상태의 스레드들을 잠깐만 실행 상태로 변경해서 실행한다. 이 과정을 반복한다.
참고로 자바에서는 두 상태를 구분할 수 없다.
yield() 의 작동
Thread.yield() 메서드는 현재 실행 중인 스레드가 CPU 스케쥴링 큐에 들어가 다른 스레드가 실행될 수
있도록 한다.
yield() 메서드를 호출한 스레드는 RUNNABLE 상태를 유지하면서 CPU 를 양보한다.
즉, 이 스레드는 다시 스케줄링 큐에 들어가면서 다른 스레드에게 CPU 사용 기회를 넘긴다.
참고로
yield()는 운영체제 스케줄러에게 단지 힌트를 제공할 뿐, 강제적인 실행 순서를 지정하지 않는다.때문에, 반드시 다른 스레드가 실행되는 것도 아니다. 때문에, 양보할수도 하지 않을수도 있다.
yield() 는 RUNNABLE 상태를 유지하기 때문에, 쉽게 이야기해서 양보할 사람이 없다면,
본인 스레드가 계속해서 실행될 것이다.

Last updated