스레드란?
자바 프로그램을 실행하면 JVM 프로세스가 실행되고, JVM 프로세스 안에서 GC를 포함한 여러 개의 스레드가 실행된다.
각 스레드마다 Java Stack, PC, Native Method Stack 이 존재한다.
자바는 동일한 메모리를 읽지만 각각 작업을 수행하는 스레드들이 동시에 여러 작업을 수행한다.
스레드의 등장 배경
프로세스를 생성하려면 많은 시간과 자원이 필요하다. JVM은 벤더마다 다르지만 최소 32MB의 메모리가 필요하고, 스레드는 1MB 메모리가 필요하다.
그래서 스레드를 경량 프로세스라고도 부른다.
스레드의 종류
스레드에는 사용자 수준 스레드와 커널 수준 스레드가 있다.
사용자 수준 스레드는 사용자 라이브러리를 통해 사용자가 만든 스레드로, 스레드가 생성된 프로세스의 주소 공간에서 해당 프로세스의해 실행되고 관리된다.
커널 수준 스레드는 커널에 의해 생성되고 운영체제에 의해 직접 관리된다.
두 스레드의 차이는 스레드 제어권이다.
사용자 수준 스레드는 스레드가 생성된 프로세스에 제어되며, 커널은 프로세스 내의 사용자 스레드에 대해 알지 못한다.
커널 수준 스레드는 OS에 의해 직접 관리된다.
OS 스레드를 실행하기 위해서는 커널의 CPU 스케줄러가 스레드를 CPU에 스케줄링 해야 한다.
하지만 CPU 스케줄러는 커널의 일부이기 때문에, 사용자 스레드를 알지 못하기 때문에, 사용자 스레드는 커널 스레드에 매핑되어 실행된다.
즉, JVM이 스레드를 스케줄링하지 않고, OS에 스케줄링을 맡겨 OS의 스케줄링 적책을 따른다.
자바 스레드 매핑 모델
Many-To-One
다대일 모델은 여러 스레드가 하나의 커널 스레드에 매핑한다.
스레드는 사용자 공간의 스레드 라이브러리에 의해 관리된다.
만약, 사용자 스레드 중 한 스레드가 Blocking 되면, 전체 프로세스가 Block이 된다.
한 번에 하나의 스레드만이 커널에 접근할 수 있기 때문이다.
자바 초기 버전의 스레드 모델인 Green 스레드가 다대일 모델을 사용하였다.
One-To-One
일대일 모델은 각 사용자 스레드를 하나의 커널 스레드에 매핑한다.
그렇기 때문에 하나의 스레드가 Blocking 되도, 다른 스레드가 실행될 수 있기 때문에 병렬 실행에 용이하다.
이 모델의 단점은 사용자 스레드를 만들기 위해선 커널 스레드를 만들어야 하는 것이다.
Many-To-Many
다대다 모델은 여러 개의 사용자 스레드를 여러 커널 스레드로 멀티 플렉스 하는 것이다.
이전 두 모델의 단점을 절충한 방법으로, 개발자는 필요한 만큼 사용자 스레드를 생성하고, 그에 상응하는 커널 스레드가 병렬로 수행될 수 있다.
하나의 스레드가 Blocking 되도 커널은 다른 스레드의 수행을 스케줄 할 수 있다.
현재의 JVM은 1:1 스레드 매핑을 사용한다.
즉, 하나의 자바 스레드가 하나의 운영체제 스레드에 직접 매핑되는 방식이다.
스레드 생성시 OS 스레드 생성 과정
- 자바 코드에서 Thread 를 생성하고, start() 메서드를 호출하면,JVM은 내부적으로 네이티브 메서드인 start0()을 호출한다.
- start0()은 네이티브 코드로 구현되어 있고, 네이티브 API를 사용하여 새로운 OS 스레드를 생성한다. 해당 OS 스레드는 운영체제 스케줄러에 의해 관리된다.
- 생성된 OS 스레드와 자바 스레드 사이의 매핑은 JVM 내부에서 관리된다.
자바 스레드 동작시 CPU는 어떻게 동작하는가?
자바 스레드가 start()를 호출하면, JVM은 해당 자바 스레드를 OS 스레드와 매핑하고 실행한다.
이후 운영체제의 스레드 스케줄러가 해당 OS 스레드를 관리하며, CPU에서 실행된다.
즉, 자바 스레드의 실행과 관리는 JVM 내부의 스레드 스케줄러에 의해 이루어진다.
OS 스레드 스케줄러는 JVM 내부 스레드 스케줄러에 의해 실행되는 스레드를 CPU에 할당합니다.
자바 스레드 sleep()시, CPU는 어떻게 되는가?
sleep() 메서드 호출 시 자바 스레드는 지정된 시간 동안 대기 상태가 됩니다.
OS 스레드는 자바 스레드에 매핑되어 실행되므로, OS 스레드도 대기 상태가 됩니다.
OS 스레드도 대기상태가 되므로, 해당 시간 동안 다른 작업이 실행됩니다. 즉 CPU를 해당 스레드에 할당하지 않습니다.
즉, OS 스케줄러의 대상에서도 제외됩니다.
시나리오 가정
웹 애플리케이션을 사용할 때 사용자가 request를 보냈다.
임의적으로 server 에서 해당 request를 처리하고 있는 스레드에게 중간에 sleep()을 걸어버렸다.
그럼 어떤 결과가 발생할까?
- 클라이언트 입장 : 클라이언트는 서버에 요청을 보내고 응답을 기다리는데, 해당 요청을 처리하는 스레드가 sleep()이 걸려, 일정시간동안 아무런 응답을 주지 않게 된다. 결국, 클라이언트는 일정 시간 응답이 없다고 판단하여 타임 아웃 혹은 연결 실패 등의 에러 메시지를 받게 된다.
- 서버 입장 : 해당 요청을 처리하는 스레드가 sleep()이 걸리면, 그 스레드는 다른 요청을 받지 못한다. 서버 내 다른 스레드들은 정상적으로 작업을 수행할 수 있지만, 해당 스레드는 sleep()이 완료되기 전까지 해당 요청에 대한 응답을 보내지 못한다.
- CPU 입장 : 해당 스레드를 작업 하다가, sleep() 이걸리면 해당 스레드와 관련된 연산을 중지하고 다른 작업을 처리한다.
'Back-end' 카테고리의 다른 글
Servlet, ServletContainer vs DispatcherServlet (0) | 2023.08.24 |
---|---|
volatile (0) | 2023.08.23 |
동기식 vs 비동기식, Blocking vs Non-Blocking (0) | 2023.08.23 |
2023.08.22 TIL (0) | 2023.08.22 |
SOLID 원칙, 객체지향 프로그래밍 (0) | 2023.08.22 |