JAVA/비동기 프로그래밍

[Java] Java 쓰레드 기본 API

꾸준함. 2024. 2. 5. 21:36

개요

해당 게시글에 앞서 아래 포스팅을 읽고 오시면 이해하는데 도움이 되실 것입니다.

https://jaimemin.tistory.com/2369

 

[Java] 쓰레드 생성 및 실행 구조

개요해당 게시글에 앞서 아래 포스팅을 읽고 오시면 이해하는데 도움이 되실 것입니다. https://jaimemin.tistory.com/2357 [OS] 운영체제 기초Process vs Thread Process 프로그램의 실제 실행을 의미하며 프로그

jaimemin.tistory.com

 

1. sleep()

1.1 개요

 

  • 지정된 시간 동안 현재 쓰레드의 실행을 일시 정지하고 Timed Waiting 상태로 빠졌다가 시간이 지나면 Ready To Run 상태로 전환
  • native 메서드이기 때문에 OS에 위임하며 시스템 콜을 통해 커널 모드에서 수행 후 유저 모드로 전환

 

InterruptedException

  • sleep() 메서드로 인해 Timed Waiting 상태로 빠진 쓰레드가 interrupt 될 경우 InterruptedException 예외 발생
  • InterruptedException 발생 시 쓰레드는 Ready To Run 상태로 전환되어 실행 상태를 대기

 

1.2 작동 방식

 

  • sleep()이 호출되면 OS 스케줄러는 현재 쓰레드를 지정된 시간 동안 Timed Waiting 상태로 전환하고 context switching 되어 타 쓰레드 혹은 프로세스에게 CPU를 사용하도록 스케줄링
  • 대기 시간이 끝나면 쓰레드 상태는 Ready To Run 상태로 전환되고 CPU가 실행을 재개할 때까지 대기
  • 실행 상태가 되면 쓰레드는 남은 지점부터 실행을 다시 시작
  • Timed Waiting 상태인 쓰레드에게 interrupt 발생 시 현재 쓰레드는 Ready To Run 상태로 전환되어 InterruptedException 예외를 처리

 

1.3 sleep(0) vs sleep(n)

 

sleep(0)

  • 쓰레드가 커널 모드로 전환된 후 OS 스케줄러는 현재 쓰레드와 동일한 우선순위의 쓰레드가 있을 경우 Timed Waiting 상태인 쓰레드에게 CPU를 할당함으로 context switching 발생
  • 만약 우선순위가 동일한 Timed Waiting 쓰레드가 없을 경우 OS 스케줄러는 현재 쓰레드에게 계속 CPU를 할당해서 context switching 없이 모드 전환만 발생
  • 즉 sleep(0) 메서드의 결과를 유저는 예측하기 쉽지 않음

 

sleep(n)

  • 쓰레드가 커널 모드로 전환 후 스케줄러는 조건에 상관없이 현재 쓰레드를 Timed Waiting 상태로 두고 다른 쓰레드에게 CPU를 할당함으로 모드 전환과 함께 context switching을 보장

 

따라서 타 쓰레드에게 CPU 사용 시간을 양보하는 것이 목적이라면 sleep(0)이 아니라 sleep(1)과 같이 sleep(n)을 호출하는 것을 권장합니다.

 

1.4 주의사항

 

  • 동기화 메서드 영역 내 Timed Waiting 상태인 쓰레드는 획득한 Lock을 잃지 않고 계속 유지
  • sleep() 메서드는 native 메서드이기 때문에 OS 스케줄러 및 시스템 기능에 위임
    • 시스템 부하가 많고 적음에 따라 매개변수로 전달한 시간보다 오래 혹은 적게 TIMED_WAITING 상태를 유지할 수 있음

 

2. join()

2.1 개요

 

  • 한 쓰레드가 다른 쓰레드가 종료될 때까지 실행을 중지하고 Waiting 상태에 들어갔다가 쓰레드가 종료되면 Ready To Run 상태로 전환 
    • 매개변수로 밀리초 전달 시 sleep()과 동일하게 Timed Waiting 상태에 들어갔다가 지정된 시간 경과 후 Ready To Run 상태로 전환

 

  • 쓰레드의 순서를 제어하거나 다른 쓰레드의 작업을 기다리는 등 순차적인 흐름을 구성할 때 사용
  • 내부적으로 native 메서드인 wait()과 notify()를 통해 제어
    • wait() 메서드를 통해 Waiting 상태로 전환하며 다른 쓰레드가 종료되었다고 notify() 메서드를 호출할 때까지 Waiting 상태 유지

 

InterruptedException

  • 쓰레드가 join 메서드를 호출하여 Waiting 상태로 전환됐을 때 다른 쓰레드에서 해당 쓰레드에게 interrupt 신호를 보낼 수 있으며 이때 InterruptedException 예외 발생

 

2.2 작동 방식

 

  • join() 호출 시 OS 스케줄러는 join 메서드를 호출한 쓰레드를 Waiting 상태로 전환하고 호출 대상 쓰레드에게 CPU를 사용하도록 스케줄링
  • 호출 대상 쓰레드의 작업이 종료되면 join()을 호출한 쓰레드는 Ready To Run 상태로 전환되고 CPU가 스케줄링할 때까지 대기
  • join 메서드를 호출한 쓰레드가 Ready To Run 상태에서 Running 상태로 전환되면 해당 쓰레드는 context switching이 된 시점부터 다시 시작
  • 호출 대상 쓰레드가 다수일 경우 각 쓰레드의 작업이 종료될 때까지 join()을 호출한 쓰레드는 대기와 실행을 재개하는 흐름을 반복
  • join()을 호출한 쓰레드가 interrupt 신호를 받으면 InterruptedException 예외가 발생하며 해당 쓰레드는 Waiting 상태에서 Ready To Run 상태로 전환되어 예외 처리

 

2.3 join() 예시

 

 

 

흐름

  • 메인 쓰레드가 thread가 완료될 때까지 대기
  • thread 완료 후 메인 쓰레드 계속 진행
  • longRunningThread 실행
  • interruptingThread 실행 후 longRunningThread에 interrupt 신호 보냄
  • longRunningThread에서 InterruptedException 발생하여 메인 쓰레드에 interrupt 신호 보냄
  • 메인 쓰레드에서 InterruptedException 발생하여 RuntimeException 발생

 

3. interrupt(), interrupted(), isInterrupted()

3.1 interrupt() 개요

 

  • 특정한 쓰레드에게 interrupt 신호를 보내 해당 쓰레드의 실행을 중단, 작업 취소, 강제 종료 등으로 사용
  • 쓰레드는 인터럽트 발생 여부를 확인할 수 있는 상태 값인 interrupted를 가지고 있으며 디폴트 값은 false
  • 쓰레드는 자기 자신 및 다른 쓰레드에 interrupt 신호 보내기 가능
  • interrupt()하는 횟수는 제한이 없으며 인터럽트 할 때마다 쓰레드의 interrupted 상태는 true로 변경

 

3.2 interrupted() 개요

 

  • 쓰레드의 interrupt 상태를 반환하는 정적 메서드
  • 현재 인터럽트 상태가 true일 경우 true를 반환한 뒤 interrupt 상태를 false로 초기화
    • 인터럽트 해제하는 역할
    • 인터럽트를 강제로 해제했기 때문에 다시 interrupt()를 호출하여 인터럽트 상태를 유지 가능
    • 인터럽트 해제 시 다른 곳에서 쓰레드에 대한 인터럽트 상태를 체크하는 곳이 있다면 별도 처리 필요할 수도 있음
      • ex) 동기화 작업이 필요할 경우 추가 로직 필요할 수도 있음

 

3.3 isInterrupted() 개요

 

  • 쓰레드의 인터럽트 상태를 반환하는 인스턴스 메서드
  • 해당 메서드는 단순히 상태 확인용이기 때문에 인터럽트 상태를 변경하지 않고 계속 유지
    • 단순히 상태 확인을 하는 용도라면 값을 변경하는 interrupted() 보다는 isInterrupted()를 권장

 

3.4 InterruptedException

 

  • InterruptedException 예외 발생 시 interrupted() 메서드처럼 interrupted 상태를 false로 초기화
    • 따라서 interrupted()처럼 다른 곳에서 인터럽트 상태를 참조하고 있다면 예외 구문에서 대상 쓰레드에 다시 interrupt 신호를 보내 true로 바꿔야 할 수도 있음

 

  • Thread.sleep(), Thread.join(), Object.wait(), Future.get(), BlockingQueue.take() 상태인 쓰레드 대상으로 interrupt() 메서드가 호출되면 InterruptedException 예외 발생

 

3.5 예시 코드

 

 

흐름

  • 메인 쓰레드에서 thread에 interrupt 신호를 보낼 때까지 thread 실행
    • isInterrupted() 메서드는 상태 확인용이기 때문에 interrupt 상태 값이 변경되지는 않음

 

  • thread가 메인 쓰레드로부터 interrupt 신호를 받아 Thread.interrupted() 결과가 true로 반환되었고 interrupt 상태가 false로 초기화
  • thread가 자기 자신에게 interrupt 신호 보내어 interrupt 상태가 true로 전환

 

4. name(), currentThread(), isAlive()

4.1 name() 개요

 

  • 멀티 쓰레드 환경에서 디버깅할 때 어떤 쓰레드가 실행 중인지 알아야 할 경우가 있는데 이때 쓰레드에 구체적인 이름을 부여하면 손쉽게 실행 중인 쓰레드를 찾을 수 있음
  • 쓰레드 생성 시 JVM에서 자동으로 이름을 부여하지만 쓰레드명이 Thread-n과 같은 형식이므로 특정 짓기 쉽지 않음
    • 메인 쓰레드명은 main

 

  • 쓰레드 객체 생성자에 매개변수를 넘기거나 setName() 메서드를 통해 쓰레드 이름을 부여할 수 있으며 getName() 메서드를 통해 쓰레드명을 참조할 수 있음

 

4.2 currentThread() 개요

 

  • Thread 클래스의 정적 메서드로써 현재 실행 중인 쓰레드 instance에 대한 참조를 반환
  • 현재 CPU에서 실행 중인 쓰레드의 이름을 파악 혹은 쓰레드 비교를 위해 사용
    • Thread.currentThread().getName()
    • Thread.currentThread == thread

 

4.3 isAlive() 개요

 

  • 쓰레드가 종료되었는지 여부를 확인 가능
    • 쓰레드의 start() 메서드가 호출된 이후 아직 종료되지 않은 경우 쓰레드가 활성 상태로 간주하고 true로 반환 
    • isAlive() 결과가 false라면 종료

 

5. priority()

5.1 priority() 개요

 

  • 쓰레드 개수가 CPU 개수보다 많을 경우 OS 스케줄링이 필요한데 자바 런타임은 고정 우선순위 선점형 스케줄링(fixed-priority pre-emptive scheduling) 알고리즘을 지원
    • 해당 알고리즘은 실행 대기 상태의 쓰레드 중에서 상대적으로 우선순위가 높은 쓰레드를 예약

 

5.2 우선순위 개념

 

  • Java 쓰레드의 우선순위는 정수 값과 비례
    • public static int MIN_PRIORITY는 최소 우선순위이며 값은 1
    • public static int NORM_PRIORITY는 기본 우선순위이며 값은 5
    • public static int MAX_PRIORITY는 최대 우선순위이며 값은 10

 

  • 쓰레드가 생성될 때 우선순위 값이 정해지며 default 값은 NORM_PRIORITY인 5
    • setPriority 메서드를 통해 쓰레드의 우선순위를 1~10 사이 숫자로 설정 가능
    • getPriority 메서드를 통해 쓰레드의 우선순위를 참조

 

  • OS 스케줄러는 우선순위가 높은 쓰레드를 실행하다가 해당 쓰레드가 실행 불가능 상태로 전환될 경우 우선순위가 낮은 쓰레드를 실행
    • 우선순위가 같을 경우 라운드 로빈 방식으로 다음 쓰레드를 선택

 

주의: 스케줄러가 반드시 우선순위가 높은 쓰레드를 실행한다고 보장할 수 없음

  • OS마다 각기 다른 정책들이 있으며 starvation 상태를 피하기 위해 스케줄러가 우선순위가 낮은 쓰레드를 선택할 수도 있음

 

세 개의 쓰레드를 생성하고 각각에 최대, 기본, 최소 우선순위를 할당했을 때, 우선순위가 높은 순서로 실행이 완료될 것으로 예상됩니다.

그러나 아래 코드를 살펴보면 예상과 달리 OS 스케줄러의 내부 정책에 의해 결괏값이 매번 달라지는 것을 확인할 수 있습니다.

 

 

참고

자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1] - 정수원 강사님

반응형

'JAVA > 비동기 프로그래밍' 카테고리의 다른 글

[Java] 동기화 기법  (1) 2024.03.04
[Java] 동기화 개념  (0) 2024.02.24
[Java] Java 쓰레드 활용  (1) 2024.02.21
[Java] 쓰레드 생성 및 실행 구조  (0) 2024.01.31
[OS] 운영체제 기초  (0) 2024.01.19