포스트

자바와 스레드

자바와 스레드


스레드는 프로세스보다 가벼운 스케줄링 단위입니다. 프로그램에서 스레드를 구현하기 위해서는 3가지 방법이 존재합니다.

  1. 커널 스레드 구현(커널 스레드와 프로그램 스레드의 1:1 매핑)
  2. 사용자 스레드 구현(커널 스레드와 프로그램 스레드의 1:N 매핑)
  3. 하이브리드 구현(커널 스레드와 프로그램 스레드의 M:N 매핑)


자바에서는 스레드 구현 방법을 명세로 정해놓지는 않았지만, 주류 가상 머신들은 운영 체제의 기본 스레드 모델(1:1 매핑)을 기반으로 사용하는 방법을 채택했습니다. 개발자는 운영 체제별 스레드 모델에 종속되는 것이 아니라, Thread 인터페이스를 활용해서 개발을 하면 되기에 편리하게 스레드를 활용할 수 있습니다.
예를 들어, 서블릿을 이용하면 HTTP 요청 하나가 스레드 하나에 직접 매핑되기에, 동기화와 동시성이라는 복잡한 개념 없이도 프로그램 로직을 구성할 수 있습니다.

가상 스레드


현대에서는 MSA 구조에 기반한 서비스가 주류를 이루게 되고, 요청당 실행 시간이 매우 짧아지면서 수가 많아지게 됐습니다. 이런 상황에서 커널 스레드를 계속 활용하는 것은 컨텍스트 스위칭과 스케줄링 비용을 계속 키우는 일입니다. 기존 자바 웹 서버의 스레드 풀 용량은 수십에서 200개 정도이며, 이런 스레드 풀에 수백만 개의 요청이 들어온다면 시스템의 전환 손실이 상당할 것입니다.

코루틴


컨텍스트 스위칭의 비용이 비싼 이유는, 해당 메서드의 호출 스택에서 사용되는 모든 데이터를 메모리와 레지스터에 다시 불러와야 하기 때문입니다. 스레드마다 작업이 다르기에 해당 스레드의 작업 데이터를 이전 작업이 멈춘 시점과 똑같이 복원해야 하기 때문입니다.
대부분의 사용자 스레드는 협력적 스케줄링 형태로 설계됐고, 이는 코루틴이라고 불렸습니다. 코루틴은 콜 스택을 완벽히 보관하고 복원하기에 오늘날에는 스택풀 코루틴이라고 부릅니다(이후에 등장하는 스택리스 코루틴과 구별하기 위함)
코루틴의 가장 큰 장점은 가볍다는 것입니다. 커널 스레드보다 훨씬 가벼우며, 64 비트 리눅스의 스레드 스택은 1MB 인데 반해 코루틴의 스택은 수백 바이트에서 수 킬로바이트 사이입니다.
따라서, JVM 에서 스레드 풀의 용량은 200 개 정도에 불과하지만 코루틴은 수십만 개가 공존할 수 있습니다.

자바의 가상 스레드


OpenJDK 에서는 2017년 자바 스레드 모델을 보완하기 위해 룸 프로젝트를 시작했고, 이는 JDK 21 에 가상 스레드라는 이름으로 결실을 맺었습니다. 다음은 오라클에서 설명하는 자료를 번역한 내용입니다.

  • 경량 또는 사용자 모드 스레드로, 운영 체제가 아닌 자바 가상 머신이 스케줄링한다.
  • 파이버(가상 스레드)는 용량을 적게 차지하고 작업 전환 부하가 미미하며 동시에 수백만 개를 다룰 수 있다.


사용자 스레드의 부활을 위해 수행된 룸 프로젝트는 기존 스레드 모델과 공존할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
+------------------------------+
|            JVM               |
|                              |
|   +---------------------+    |
|   |  플랫폼 스레드 풀    |    |
|   | +-----------------+ |    |
|   | | 플랫폼 스레드 1  |<----+----> 작업 1
|   | +-----------------+ |    |
|   | | 플랫폼 스레드 2  |<----+----> 가상 스레드 1,2 와 매핑
|   | +-----------------+ |    |
|   |         ...         |    |
|   +---------------------+    |
|                              |
|   +---------------------+    |
|   |   가상 스레드 풀     |    |
|   | +-----------------+ |    |
|   | | 가상 스레드 1   |<-- 작업 2
|   | +-----------------+ |    |
|   | | 가상 스레드 2   |<-- 작업 3
|   | +-----------------+ |    |
|   |         ...         |    |
|   +---------------------+    |
|                              |
+------------------------------+



하단의 이미지는 마리아 DB 에서 JDBC 커넥터에 가상 스레드를 적용해 테스트한 결과입니다.

Desktop View

참고 : https://mariadb.com/resources/blog/benchmark-jdbc-connectors-and-java-21-virtual-threads/


4가지 상황에서 가상 스레드를 이용한 경우, 기존 커넥터 대바 4배 이상 빠른 것을 확인할 수 있습니다. R2DBC 는 스레드 대신 논 블로킹을 활용한 커넥터입니다. 가상 스레드를 적용한 JDBC 커넥터가 모든 시나리오에서 최고의 성능을 보여주는 것을 알 수 있습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.