JetBrains는 Project Loom의 세 가지 기능인 가상 스레드, 스코프 값, 구조적 동시성을 결합해 동시성 비용과 Java에서의 관리 복잡성을 줄이는 방식을 설명한다. 또한 이 글은 Structured Concurrency가 Java 27에서 여전히 실험적 기능인 반면, Virtual Threads는 Java 21부터, Scoped Values는 Java 25부터 안정화되었다고 설명한다.
JetBrains는 2026년 8월 27일에 게시한 블로그 글에서 IntelliJ IDEA의 Project Loom을 구성하는 세 가지 요소인 Virtual Threads, Scoped Values, Structured Concurrency를 실용적으로 살펴본다. 핵심은 별도의 API를 추가하는 것이 아니라, Java 동시성 애플리케이션에서 서로 연관된 세 가지 문제인 확장성, 작업 간 컨텍스트 전달, 스레드와 오류의 수명 주기 관리를 해결하는 데 있다.
이 글은 멀티스레드 코드를 작성할 때 스레드 누수, 예외 무시, 경쟁 조건, 취소 지연이 여전히 발생할 수 있다고 설명한다. 또한 스레드 풀과 CompletableFuture에 전통적으로 의존하면 취소 및 오류 처리 로직이 여러 분기에 분산될 수 있으며, 이로 인해 새로운 동시 작업을 추가할 때 해당 로직이 업데이트되지 않을 가능성이 커진다.
가상 스레드는 대기 비용을 줄인다
가상 스레드는 JEP 444를 기반으로 하며 Java 21부터 안정화되었다. 운영 체제 스레드에 연결된 플랫폼 스레드와 달리 가상 스레드는 JVM이 관리하므로 훨씬 낮은 비용으로 생성할 수 있다. JetBrains에 따르면 가상 스레드 생성에는 밀리초가 아니라 마이크로초가 걸리며, 데이터베이스, 네트워크 연결, 파일 또는 동기화 메커니즘을 기다리며 중단될 때 플랫폼 스레드를 해제한다.
따라서 가상 스레드는 블로킹 작업에 의존하는 워크로드에 특히 적합하며, 스레드 풀 크기를 미리 조정해야 할 필요를 줄여 준다. 이 글은 또한 Java 24가 JEP 491을 통해 synchronized 메서드나 문 내부에서 블로킹된 가상 스레드가 플랫폼 스레드를 계속 점유하는 대신 해제할 수 있도록 개선했다고 설명한다.
ThreadLocal 문제 없이 공유 컨텍스트 사용하기
JEP 506에 따라 Java 25부터 안정화된 Scoped Values는 다른 문제를 해결한다. 애플리케이션은 세션 ID나 추적 ID 같은 데이터를 요청의 여러 부분에 전달해야 하는 경우가 많다. 일반적으로는 ThreadLocal을 사용했지만, ThreadLocal 값은 변경 가능하고 수동으로 제거하지 않으면 스레드의 수명에 계속 연결되어 있어 메모리 누수나 보안 문제가 발생할 수 있다. JetBrains는 개발자가 직접 사용하지 않더라도 Spring과 같은 프레임워크가 내부적으로 ThreadLocal을 사용할 수 있다고 지적한다.
ScopedValue는 특정 범위 안에서 값을 한 번 바인딩한 다음, 해당 범위 안에서 실행되는 코드에 자동으로 제공하고 범위가 끝나면 정리하는 모델을 제공한다. 범위 내부에서는 바인딩을 변경할 수 없으며, 구조적 동시성과 함께 사용하면 컨텍스트를 명시적으로 전달하지 않아도 자식 작업으로 값이 전달된다. 실질적으로 이는 병렬 작업을 실행할 때 개발자가 추적해야 하는 제어 지점의 수를 줄여 준다.
구조적 동시성은 작업을 명확한 수명에 연결한다
JEP 533을 기반으로 하는 Structured Concurrency는 동시성 코드의 구조적 문제를 해결하는 것을 목표로 한다. 그러나 Java 27에서 여전히 일곱 번째 프리뷰 단계에 있으므로 JetBrains는 아직 프로덕션 환경에서 사용하는 것을 권장하지 않는다. 이 개념은 서로 연결된 작업 집합을 소유자와 수명, 명확한 실패 정책을 가진 하나의 작업 단위로 취급하는 데 기반한다.
JetBrains가 제시한 예제에서 애플리케이션은 고객 프로필을 구성하기 위해 고객의 주문 기록과 제품 추천을 병렬로 가져온다. StructuredTaskScope를 사용하면 2초의 시간 제한을 지정하고 두 작업 모두의 성공을 요구하며, 둘 중 하나가 실패하거나 시간 제한이 만료되면 다른 작업을 자동으로 취소할 수 있다. 예외 처리 분기마다 cancel() 호출을 반복하는 대신 취소 정책이 작업 범위의 일부가 된다.
이 글은 모든 작업의 성공이 필요하지 않은 다른 경우도 설명한다. 애플리케이션이 두 개의 캐시에서 추천 정보를 가져오고 첫 번째 성공적인 응답이면 충분한 경우에는 anySuccessfulOrThrow()를 사용할 수 있다. 성공적인 결과가 도착하면 범위가 닫히고 다른 작업이 자동으로 취소되며, 두 작업이 모두 실패하면 실패 원인을 설명하는 예외가 발생한다.
IntelliJ IDEA 내부에서 무엇이 달라지는가?
이점은 코드 작성 방식에만 국한되지 않는다. IntelliJ IDEA 2026.1부터 StructuredTaskScope 내부에서 생성된 가상 스레드는 해당 범위를 나타내는 컨테이너로 묶이며, 디버거에서 부모 작업과 자식 작업 간의 관계 구조를 확인할 수 있다. 따라서 스레드 덤프는 일반 풀의 스레드를 평면적인 목록으로만 보여 주는 것이 아니라, 동일한 요청에 속한 작업을 더 잘 파악할 수 있는 지표를 제공한다.
이 기능을 사용하려면 개발자는 Java 27이 필요하며, 이 글에 따르면 Java 27은 Early Access 버전이다. 또한 실험적 기능을 사용하도록 언어 수준을 설정해야 한다. IntelliJ IDEA는 프로젝트 설정에서 JDK 다운로드를 지원하며, SDKMAN!이나 asdf 같은 도구로 JDK 버전을 관리할 때 인라인 힌트도 표시한다. sts 라이브 템플릿을 사용하면 StructuredTaskScope의 기본 구조를 생성할 수 있다.
certi.news의 편집자 관점
여기서 실제 변화는 동시성 관리의 일부가 개발자의 수동 책임에서 벗어나, 코드 구조 자체가 작업의 수명과 실패 정책을 표현하는 모델로 이동한다는 점이다. Virtual Threads는 확장 비용을 해결하고, Scoped Values는 컨텍스트 전달을 제어하며, Structured Concurrency는 취소와 오류 전파를 예측 가능하게 만들려고 한다. 이러한 기능이 함께 사용되면 여러 대기 작업을 병렬로 실행하는 애플리케이션에서 반복 코드를 줄일 수 있다.
그러나 발전 단계의 한계는 분명하다. Structured Concurrency는 아직 안정화되지 않았고, 이 글이 게시될 당시 Java 27은 Early Access 버전이었다. 따라서 이 블로그 글이 모든 애플리케이션에서 자동으로 성능 향상이 이루어진다는 것을 입증하는 것은 아니며, 이러한 모델과 함께 사용하는 라이브러리와 프레임워크의 동작을 테스트해야 할 필요성도 없애지 않는다. 현재 개발자에게 제공되는 가치는 이 패턴을 이해하고 안전하게 실험하는 데 있으며, API가 안정화되고 충분한 운영 경험이 축적될 때까지 실험적 기능을 프로덕션에서 제외하는 것이 바람직하다.