희디비
[Java] Join, Interrupt, Yield 본문
조인 ( Join )
조인은 왜 필요 할까요?
간단한 작업을 통해 이해 해보도록 하겠습니다.
스레드A는 1~10의 합을 구하는 작업을 하고 메인 스레드는 그 결과를 출력 합니다.
public static void main(String[] args) throws InterruptedException {
AddOneToTen task = new AddOneToTen();
Thread threadA = new Thread(task, "Thread-A");
threadA.start();
System.out.println("결과 값 : " + task.result);
System.out.println("Thread-A 상태 : " + threadA.getState());
}
static class AddOneToTen implements Runnable{
int result;
@Override
public void run() {
for(int i = 1; i <= 10; i++){
result += i;
}
}
}
원하는 결과는 1~10의 합인 55가 출력 되기를 기대 합니다.
원하던 결과 값이 아니고 0이 출력 됩니다. 왜 그럴까요?
멀티스레드는 각자 작업을 수행 합니다.
메인스레드가 result를 확인 했을 때 result 값이 계산이 되지 않은 것 입니다.
문제를 해결 하려면 스레드A 작업이 완료 될 때 까지 기다려야 합니다.
이때 사용하는 것이 조인(Join) 입니다.
조인은 스레드의 상태가 Terminated 될때 까지 Waiting 상태로 기다립니다.
스레드A가 종료 되고 메인 스레드로 돌아와서 값을 확인 하기 때문에 원하는 값을 얻을 수 있습니다.
( 참고 + join(millis) 파라미터로 시간을 주면 일정 시간만 기다리는 것도 가능 합니다. )
public static void main(String[] args) throws InterruptedException {
AddOneToTen task = new AddOneToTen();
Thread threadA = new Thread(task, "Thread-A");
threadA.start();
threadA.join();
System.out.println("메인 스레드 상태 : " + Thread.currentThread().getState());
System.out.println("Thread-A 상태 : " + threadA.getState());
System.out.println("결과 값 : " + task.result);
}
조인을 이용하여 스레드A가 계산을 완료 할때 까지 기다린 후 결과 값을 표시 합니다.
이제 원하는 값을 찾을 수 있습니다.
스레드의 진행 상태를 표현 한다면 다음과 같습니다.
메인 스레드가 다른 스레드의 작업 결과가 필요 하다면 Join을 통해서 상대 스레드가 종료 될때 까지
상태를 Waiting 상태로 있다가 작업 스레드가 종료 (Terminated) 된다면 다시 메인 스레드가
Runnable 상태로 바뀌어 작업 결과를 사용 할 수 있습니다.
만약 메인 스레드에서 스레드A 작업을 기다려 줄 수 없고 급한 일이 생겨 빨리 작업을 끝내야 한다면 어떻게 할까요?
스레드A에게 급한 일이 있으니 그만 실행 해달라고 요청을 보내야 합니다.
이때 사용 하는 것이 인터럽트(interrupt) 입니다.
인터럽트 ( Interrupt )
인터럽트의 동작 방식은 스레드 A에 interrupt를 걸면 스레드 내부의 필드인 interrupt가 true로 변합니다.
( threadA.interrupt() -> threadA.interrupt = true ) 인터럽트 상태 일 경우 다음과 같이 행동 합니다.
Thread.interrupted()
내 스레드의 인터럽트 상태를 체크 하고 인터럽트 상태 일 경우 인터럽트를 해제 하고 ( interrupt = false )
정상 흐름으로 진행 됩니다.
InterruptException 발생 시키는 메서드
Thread.sleep은 스레드의 상태를 Timed_Waiting 상태로 바꿉니다.
스레드 상태를 Waiting, Timed_Waiting 으로 바꾸는 메서드는 InterruptException을 발생 시킵니다.
만약 인터럽트 상태 라면 ( threadA.interrupt = true ) 이 메서드들은 InterruptException을 발생 시키고
인터럽트 상태를 해제 시키고 스레드의 상태도 Waiting 상태에서 Runnable 상태로 깨어 납니다.
이후의 흐름은 정상 흐름이 됩니다.
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(100);
log("작업 중단 지시 thread.interrupt()");
thread.interrupt();
log("work 스레드 인터럽트 상태1 = " + thread.isInterrupted());
}
static class MyTask implements Runnable {
@Override
public void run() {
while(!Thread.interrupted()){
log("작업 중");
}
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
try {
log("자원 정리");
Thread.sleep(1000);
log("자원 종료");
} catch (InterruptedException e){
log("자원 정리 실패");
}
}
}
work 스레드는 인터럽트가 발생 할때 까지 작업을 하고 이후엔 자원을 정리 합니다.
work 스레드에 인터럽트를 걸고 바로 상태를 확인 해보니 인터럽트 상태 입니다.
( work 스레드가 먼저 Thread.interrupted() 를 통해 상태를 바꾼다면 false로 나올 수 있습니다.
멀티 스레드는 실행 순서를 보장 하지 않기 때문 입니다. )
인터럽트로 work 스레드의 반복 되는 작업을 중지 시키고 인터럽트 상태를 false로 바꾸게 됩니다.
이후엔 정상흐름 으로 전환 됩니다.
Thread.sleep()은 InterruptException을 발생 시키지만 인터럽트 상태가 Thread.interrupted로 인해
해제 되었으므로 InterruptException은 발생 하지 않았습니다.
Interrupt는 Waiting, Timed_Waiting 상태로 바꾸는 메서드( ex) Join, Sleep )를
InterruptException을 발생 시키고 인터럽트 상태를 해제 시킵니다.
인터럽트를 통해 다른 스레드의 작업을 중단 시킬 수 있었습니다.
만약 인터럽트 상태만 확인 하고 인터럽트 상태를 false로 바꾸고 싶지 않다면 밑에 코드를 사용 하시면 됩니다.
Thread.currentThread().isInterrupted()
Yiled ( 양보 )
while(true){
if(queue.isEmpty()){
continue;
}
}
큐의 내용을 출력 하는 스레드가 있다고 가정 해보겠습니다.
큐가 비어 있다면 큐에 내용이 있을 때 까지 체크 하는 로직 입니다.
위 코드는 CPU 코어 에서 본다면 문제가 있습니다. 환경에 따라 다르지만 코어는 억 ~ 수십억의 연산을 합니다.
코어는 하나의 스레드만 사용 가능 하기 때문에 queue.isEmpty()를 계속 호출 할 것 입니다.
비유 하자면 햄버거 가게에서 내 햄버거를 다 만들었는지 계속 물어 보는 것과 같습니다.
다른 스레드가 실행 될때 까지 계속 반복 하기 때문에 비효율적 이라고 볼 수 있습니다.
다른 스레드에게 먼저 코어의 사용을 양보 할 순 없을까요? 이때 사용 하는것이 양보 ( Yiled ) 입니다.
while(true){
if(queue.isEmpty()){
Thread.yield();
}
}
yield 사용 후
위 글을 김영한 선생님 실전 자바 고급 1편을 보고 요약 한 것입니다.
자세한 내용이 궁금 하시다면 강의를 추천 드려요!
'Java' 카테고리의 다른 글
[Java] LockSupprot, ReentrantLock (1) | 2024.11.19 |
---|---|
[Java] 메모리 가시성, 임계 영역 (4) | 2024.11.15 |
[Java] 스레드의 생성과 생명주기 (0) | 2024.11.10 |
[Java] 프로세스와 스레드 (4) | 2024.11.09 |
[자바의 정석] 람다 (1) | 2023.10.30 |