[JAVA] 고급 1편 (멀티스레드와 동시성) 4 - 스레드 제어 (join, interrupt, yield)

호호빵·4일 전
0

1. Join

  • main 스레드에서 2개의 작업을 각각 다른 스레드에 지시하고, 그 결과를 받아서 처리하고 싶을 때
    → Join 사용

특정 스레드를 기다리게 하는 방법

  • Thread.sleep() 사용
    • 대기 시간을 손해보고, thread들의 수행시간이 달라지는 경우에 정확한 타이밍을 맞추기 어려움
  • 스레드의 상태가 TERMINATED가 될 때까지 계속 확인하는 방법
    • while (thread.getState() ≠ TERMINATED) { … }
    • 계속되는 반복문은 CPU 연산을 사용
  • join 사용
    • join() : 호출 스레드는 대상 스레드가 완료될 때까지 무한정 대기
    • join(ms) : 호출 스레드는 특정 시간 만큼만 대기,
      호출 스레드는 지정한 시간이 지나면 다시 RUNNABLE 상태가 되면서 다음 코드 수행
      → 기다리다 중간에 나오는 상황인데, 결과가 없다면 추가적인 오류 처리가 필요할 수 있음

join() 사용 예시

public class JoinMainV3 {
    public static void main(String[] args) throws InterruptedException {
        log("Start");
        SumTask task1 = new SumTask(1, 50);
        SumTask task2 = new SumTask(51, 100);
        Thread thread1 = new Thread(task1, "thread-1");
        Thread thread2 = new Thread(task2, "thread-2");
        thread1.start();
        thread2.start();

        // 스레드가 종료될 때 까지 대기
        log("join() - main 스레드가 thread1, thread2 종료까지 대기");
        thread1.join();
        thread2.join();

        log("main 스레드 대기 완료");
        log("task1.result = " + task1.result);
        log("task2.result = " + task2.result);

        int sumAll = task1.result + task2.result;
        log("task1 + task2 = " + sumAll);
        log("End");
    }
    
    static class SumTask implements Runnable { ... }
}
실행결과

16:46:56.803 [ main] task1.result = 1275
16:46:56.803 [ main] task2.result = 3775
16:46:56.804 [ main] task1 + task2 = 5050      ← 정확하게 출력됨
  • join()을 실행하게되면 main 스레드는 thread-1, thread-2가 종료될 때까지 기다림 (main 스레드는 WAITING 상태)
   log("join() 실행");   // main - RUNNABLE
   thread1.join();      // main - WAITING
                        // main - RUNNABLE
   thread2.join();      // main - WAITING
  • join(ms) 를 사용하게 되면 WAITING이 아니라, TIMED_WAITING이 됨



2. Interrupt

  • 특정 스레드의 작업을 중간에 중단하기 위해 사용

i) interrupt()

  • WAITING, TIMED_WAITING 같은 대기 상태의 스레드를 직접 깨워서, 작동하는 RUNNABLE 상태로 만들 수 있음

    InterruptedException

    • Thread.sleep(), wait(), 또는 join()와 같은 블로킹 메서드가 실행 중일 때, 스레드가 >interrupt 상태로 설정되면 발생
    • if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
  • 특정 스레드의 인스턴스에 interrupt() 메서드를 호출

    → Thread.sleep() 상태에 있던 스레드에 InterruptedException 발생

    → 이때 인터럽트를 받은 스레드는 대기 상태에서 깨어나 RUNNABLE 상태가 되고 코드 정상 수행

    → InterruptedException을 catch로 잡아서 정상 흐름으로 변경


ii) isInterrupted() - 인터럽트 상태 확인 (상태 변경 X)

  • 위 예제에서 sleep()과 같은 메서드를 호출하고 나서야 인터럽트가 발생하는 문제를 해결해보자
  • sleep() 없이도 while 조건문에서 isInterrupted()를 통해 interrupt() 호출 즉시 인터럽트 상태를 확인하여 반복문 탈출 가능
  • 하지만 계속 interrupt 상태를 유지하여 Thread.sleep()과 같은 코드에서 InterruptedException 발생시킴
    → 즉, 계속해서 interrupt 상태를 유지 하고 있단 뜻
    → 우리가 원하는 것은 while()문을 탈출하기 위해 딱 한번만 인터럽트를 사용하는 것이지 계속 인터럽트 상태를 유지하기를 원하는 것이 아님
    → 그래서 자바에서는 인터럽트 예외가 발생하면 스레드의 인터럽트 상태를 다시 정상(false)으로 돌림

iii) Thread.interrupted() - 인터럽트 상태 확인(상태 변경 O)

  • 직접 체크해서 사용할 때는 Thread.interrupted()를 사용해야 함
    • 스레드가 인터럽트 상태라면 true 를 반환하고, 해당 스레드의 인터럽트 상태를 false로 변경
    • 스레드가 인터럽트 상태가 아니라면 false 를 반환하고, 해당 스레드의 인터럽트 상태를 변경하지 않는다.



3. Yield

  • 어떤 스레드를 얼마나 실행할지는 운영체제가 스케줄링을 통해 결정하지만 양보하고 싶을 수 있음
  • 양보하면 스케줄링 큐에 대기 중인 다른 스레드가 CPU 실행 기회를 더 빨리 얻을 수 있음
    public class YieldClass {
    static final int THREAD_COUNT = 1000;

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        }
    }

    static class MyRunnable implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " - " + i);

                                   // 1. empty
                // sleep(1);       // 2. sleep
                // Thread.yield()  // 3. yield
            }
        }
    }
}
    1. Empty : sleep(1) , yield() 없이 호출한다. 운영체제의 스레드 스케줄링을 따른다.
    1. sleep(1) : 특정 스레드를 잠시 쉬게 한다.
    • 아주 잠깐 RUNNABLE → TIMED_WAITING 으로 변경되며 스케줄링에서 제외
    • 어떠한 스레드도 실행되지 않을 수 있음
    1. yield() : yield() 를 사용해서 다른 스레드에 실행을 양보한다.
    • 양보할 스레드가 없다면 본인 스레드가 계속 실행될 수 있음
    • 현재 실행중인 스레드가 자발적으로 CPU를 양보하여 다른 스레드가 실행될 수 있도록 함
    • yield()를 호출한 스레드는 RUNNABLE 상태를 유지하면서 CPU를 양보 (스케쥴링 큐에 들어가면서 양보)






Reference

자바 고급 1편 - 멀티스레드와 동시성

profile
하루에 한 개념씩

0개의 댓글