Java 24, 가상 스레드(Virtual Threads) 피닝(Pinning) 문제 해결로 안정성 확보!

by DD
1개월 전
조회수 12

Java 21에서 도입된 가상 스레드(Virtual Threads)의 '피닝(Pinning)' 문제로 인해 애플리케이션이 멈추는 현상 발생

synchronized 블록 내에서 I/O 작업 시 가상 스레드가 캐리어 스레드에 고정되어 성능 저하 및 데드락(Deadlock) 발생

Java 24에서 JVM 모니터 구현 방식 변경을 통해 synchronized 블록 내 피닝 문제 해결

Java 24 이상에서는 별도의 코드 변경 없이 synchronized 블록을 안전하게 사용 가능하며, 네이티브 코드(Native Code) 호출 시에는 여전히 주의 필요

피닝(Pinning) 문제의 근본 원인

Java 21-23 버전에서 가상 스레드(Virtual Threads)가 synchronized 블록 내에서 I/O 작업과 같은 블로킹 연산(Blocking Operation)을 수행할 때 피닝(Pinning) 문제가 발생했다. 기술적으로 보면, synchronized 블록은 OS 스레드(Carrier Thread)에 묶여 있는 모니터를 사용하기 때문에, 가상 스레드가 블로킹 작업 중일 때 언마운트(Unmount)될 수 없었다. 이로 인해 캐리어 스레드가 고정되어 다른 가상 스레드를 실행할 수 없게 되면서 데드락(Deadlock)이 발생할 수 있었다.

Java 24의 해결 방법: JVM 모니터 개선

Java 24에서는 JEP 491을 통해 JVM의 모니터 구현 방식을 변경하여 이 문제를 해결했다. 특히, 모니터를 OS 스레드(Carrier Thread)가 아닌 가상 스레드(Virtual Thread) 자체에 연결하도록 수정했다. 기술적으로 보면, 가상 스레드가 synchronized 블록 내에서 블로킹 작업을 수행할 때 언마운트(Unmount)되어 다른 캐리어 스레드에서 실행될 수 있게 되었다. 이로 인해 캐리어 스레드 풀(Carrier Thread Pool)의 효율성이 향상되고, 데드락(Deadlock) 발생 가능성이 줄어들었다.

ReentrantLock과 synchronized의 차이점

ReentrantLock은 LockSupport.park()를 사용하여 가상 스레드(Virtual Threads)를 위한 언마운트(Unmount)를 지원한다. 실제 사례로는, ReentrantLock을 사용하면 가상 스레드가 Thread.sleep()과 같은 블로킹 작업 중에도 캐리어 스레드를 해제할 수 있다. 반면, synchronized는 OS 스레드(Carrier Thread)에 모니터를 연결하여 피닝(Pinning) 문제를 발생시켰다. Java 24에서는 synchronized 블록의 피닝 문제가 해결되었지만, 네이티브 코드(Native Code) 호출 시에는 여전히 주의가 필요하다.

가상 스레드(Virtual Threads) 사용 가이드라인

가상 스레드(Virtual Threads)를 효과적으로 사용하기 위한 몇 가지 가이드라인이 제시되었다. 첫째, 가상 스레드는 풀링(Pooling)하지 않고, 각 작업마다 새로운 스레드를 생성해야 한다. 둘째, 동시성 제어가 필요한 경우 Semaphore를 사용해야 한다. 셋째, Java 24 이상에서는 synchronized 블록을 안전하게 사용할 수 있지만, Java 21-23에서는 ReentrantLock으로 대체하는 것이 권장되었다. 마지막으로, 서드파티 라이브러리(Third-party Libraries)의 synchronized 블록 사용에 주의하고, -Djdk.tracePinnedThreads=full 플래그를 사용하여 피닝(Pinning) 문제를 식별해야 한다.

Java Virtual Threads: The Pinning Problem and the Fix in Java 24