스레드

블러거·2026년 2월 9일

JVM

목록 보기
25/26

멀티스레드 모델

1:1

  • OS 가 제공하는 커널 스레드(커널의 복제본) 한개를 고수준 인터페이스인 경량 프로세스(스레드) 하나로 맵핑하여 사용
  • 커널의 도움으로 경량 프로세스 각각이 독립적으로 스케줄링 된다. 따라서 경량 스레드 하나가 시스템 호출에서 블록되더라도 전체 프로세스는 영향을 받지 않는다.
  • 커널 스레드를 기반으로 구현되었기에 생성, 소멸, 동기화 등 다양한 스레드 연산이 시스템 호출로 이뤄진다. 시스템 호출은 사용자 모드와 커널 모드의 전환이 발생하여 비용이 높다.
  • 경량 프로세스 하나가 커널 스레드 하나를 사용하기에 경량 프로세스의 수에 제한이 있다.

1:N

  • 넓은 의미에서 1:1 의 경량 프로세스 또한 유저 스레드이지만, 좁은 의미에서는 온전히 사용자 공간에서 구현되는 스레드 라이브러리를 사용자 스레드라 한다. 1:N 관계에서는 좁은 의미의 사용자 스레드를 만들어 사용한다.
  • 커널 모드로 전환할 필요가 없다.(고성능)
  • 유저 스레드는 시스템 커널의 지원이 필요없다. 반대로 말하면 스레드의 생성, 소멸, 동기화, 스케줄링을 사용자 프로그램이 모두 처리해야 한다. 따라서 매우 복잡하고 어렵다.
  • 어려운 난이도에 잘 사용하지 않는 모델이지만, go 와 Erlang 에서 사용하고 있다.

N:M

  • 사용자 스레드와 경량 프로세스 모두 사용한다.
  • 스레드 스케줄링, 프로세서 매핑등의 OS 기능을 사용할 수 있으면서 사용자 프로그램에서 스레드 생성, 소멸, 스케줄링 비용을 저렴하게 구현하여 감당할 수 있는 동시성 규모가 커진다.
  • 사용자 스레드의 시스템 호출은 온전히 경량 프로세스에 의해 수행되므로 프로세스 전체가 완전히 블록될 위험이 크게 줄어든다.

자바 스레드

JVM 은 따로 스레드 모델을 규정하진 않는다.
스레드 모델은 플랫폼 독립적으로 구현하려면 1:N 유저 스레드 모델을 사용해야 하는데 이는 1.2 까지 쓰이다 폐기되었다.
대부분은 OS 의 기본 스레드 모델인 1:1을 따른다.
따라서 JVM 은 스레드 스케줄링 등을 관리하지 않고 OS 에 맡긴다.

자바 스레드 스케줄링

자바는 기본적으로 선점형 스케줄링을 사용한다.
코틀린 코루틴을 사용하면 JVM 에서도 협력적 스케줄링을 사용할 수 있다.

협력적 스케줄링

  • 스레드 실행 시간을 스레드 스스로 결정. 스레드는 작업을 마치면 시스템에 알림.
  • 장점
    • 구현이 쉽다
    • 동기화 문제 발생 확률이 적다. 멈출 시점을 명확하게(코드로 지정) 알고 있기 때문이다.
      • 반면 선점형 스케줄링은 스레드가 언제 멈출지 모른다. 임계 영역에서 멈추면 동시성에 문제가 생기기 쉽다.
  • 단점
    • 스레드 실행 시간을 제어할 수 없다. 스레드가 문제가 있어 블럭되면 제어를 프로그램에 돌려주지 못해 프로그램 자체가 멈춘다.

선점형 스케줄링

  • 각 스레드의 실행 시간을 시스템이 할당
  • 스레드들은 전환 시점을 자신이 결정하지 못함. Thread::yield() 는 능동적으로 제어권을 포기하지만 제어권을 가져오는 방법은 없다.
  • 장점
    • 시스템이 실행 시간을 결정하기에, 문제가 발생한 스레드를 강제로 멈출 수 있다. 프로그램 전체가 멈추는 사태는 없다.

윈도우의 경우 과거 협력적 스케줄링 방식으로 프로세스 하나가 문제가 생기면 전체가 먹통이 되었다.
현재는 선점형 스케줄링 방식을 사용하므로 작업관리자로 특정 프로세스 하나를 종료할 수 있다.

자바 스케줄링 우선순위

자바 스레드는 제어권을 가져올 순 없지만, 스레드별로 우선순위를 가지고 시스템에게 권고할 수 있다.

Thread.MIN_PRIORITY(1) ~ Thread.NORM_PRIORITY(5) ~ Thread.MAX_PRIORITY(10)
의 10가지다.

하지만 이는 단순 권고이지 그대로 믿으면 안된다.

특히 자바 스레드 우선순위는 OS 스레드 우선순위와 연결되어야 하는데, 윈도우는 스레드 우선순위가 7가지다.
따라서 자바 스레드 우선순위는 윈도우 스레드 우선순위로 연결되면서 문제가 생긴다.
자바 우선순위 1, 2 는 THREAD_PRIORITY_LOWEST 하나로 연결되어 자바에는 1, 2 로 구분했지만 윈도우에서는 같은 우선순위를 가지는 일이 생긴다. 마찬가지로 3과 4,6과 7, 8과 9가 그렇다.

스레드 상태 전이

자바의 스레드는 항상 위 6가지 하나의 상태를 가진다.

Waiting

  • Object::wait(인자없음)
  • Thread::join(인자없음)
  • LockSupport:park()

Timed Waiting

  • Thread::sleep()
  • Object::wait(시간)
  • Thread::join(시간)
  • LockSupport::parkNanos()
  • LockSupport::parkUntil()

Blocked 와 Waiting
Waiting 은 스레드가 잠들어 누군가가 깨워야 한다.
반면 Blocked 는 락을 얻기 위해 기다리는 상태이며, 락을 얻으면 Running 으로 바뀐다.

가상 스레드

자바는 커널 스레드와 자바 스레드를 1:1 맵핑한다.
자바 스레드는 커널 스레드의 자세한 기능을 숨기는 인터페이스를 제공하여 자바 애플리케이션 개발을 쉽게 할 수 있었다.
자바 서블릿은 하나의 커널 스레드에 하나의 Http 요청을 맵핑할 수 있었다.

하지만 컴퓨터가 빨라지고, 요청이 많아지고, MSA 환경에서 IO 가 많아지면서 1:1 모델의 단점인 자원의 한계가 두드러졌다.
짧고 많은 요청은 요청 시간과 컨텍스트 스위칭 시간이 거의 비슷해지는 지경에 이르렀다.

컨텍스트 스위칭 비용

  1. 스레드 전환시 문맥 저장 비용(캐시, 메모리, 레지스터 쓰기)
  2. 스레드 복원시 문맥 복원

컨텍스트 스위칭 비용이 상대적으로 커졌다.

코루틴

스레드 전환시 문맥에 대한 저장/복원이 비용이라 한다면, 이를 애플리케이션에서 처리하면 된다.

커널에서 컨텍스트 스위칭을 위한 비용보다 애플리케이션 스택에서 처리하는게 더 빠르고 좋다.
즉, 스레드 스케줄링을 각 코루틴이 하는 것이다.

  1. 커널 모드로 들어가지 않아도 된다.
  2. 공간 자체도 적게 든다.

하지만 코루틴은 협력적 스케줄링 방식으로서 애플리케이션에서 OS 가 하던 기능을 모두 구현하기는 매우 어렵다.

코루틴 <> 협력적 스케줄링 <> 사용자 스레드 <> 가상 스레드 넷은 비슷비슷한 말이다.

스택풀 코루틴 : 코루틴이 각각 스택에 문맥을 위한 저장을 한다.
스택리스 코루틴 : 스택이 없고 상태 머신으로 스레드를 복구한다.

협력적 스케줄링을 사용하는 코루틴이 보통의 개념이다. 하지만 비협력적 스케줄링을 사용하는 코루틴도 있다.

가상 스레드


https://waterfogsw.tistory.com/72

Loom 프로젝트로 불리던 Java 가상 스레드는 일종의 코루틴이다.
자바가 복잡한 코루틴을 구현 해 준 것이다.
JDK21 에 정식 반영되었으며, 기존의 Thread 에 추가된 기능이기에 코드 변경이 적다.

가상 스레드의 애플리케이션 레벨 스케줄러는 기본적으로 ForkJoinPool 을 사용한다.

profile
안녕하세요!

0개의 댓글