스레드 제어와 생명 주기2
1. 인터럽트 - 플래그 변수 사용
특정 스레드 작업을 중간에 중단하려면 어떻게 해야할까?
플래그를 사용해 작업을 중단시켜 보자.
문제점
위 코드는 기능적으로는 문제가 없지만, 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