0Pricing
Java Academy · 강의

함정: 고정과 ThreadLocals

운반 스레드를 블로킹하지 않습니다

함정: 고정과 ThreadLocals은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

발생할 수 있는 문제

가상 스레드는 강력하지만, 확장성을 조용히 떨어뜨릴 수 있는 두 가지 위험 요소가 있습니다. 바로 고정과 ThreadLocals의 과도한 사용입니다.

이 레슨에서는 두 문제와 이를 피하는 방법을 설명합니다.

고정이란 무엇인가

고정은 가상 스레드가 블로킹된 동안 캐리어 스레드에서 마운트 해제되지 못할 때 발생합니다. 캐리어 OS 스레드가 계속 묶이므로 확장성이라는 이점이 사라집니다.

너무 많은 캐리어 스레드가 고정되면 처리량이 급격히 떨어집니다.

원인: 동기화 블록 내부에서의 차단

전형적인 원인은 synchronized 블록이나 메서드 내부에서 블로킹하는 것입니다. 모니터를 보유한 상태에서 블로킹하면 가상 스레드는 캐리어 스레드에 고정된 상태로 남습니다.

참고로 JDK 24 이상에서는(JEP 491) 이 제한이 대부분 제거되었지만, Java 21에서는 여전히 실제로 주의해야 할 문제입니다.

피해야 할 고정 예시

Java 21에서는 다음 패턴으로 고정이 발생할 수 있습니다. 모니터를 보유한 상태에서 블로킹하는 방식입니다. 코드는 정상적으로 실행되지만 확장되지는 않습니다.

public class Main {
    static final Object lock = new Object();

    static void risky() {
        synchronized (lock) {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::risky);
        t.join();
        System.out.println("Done (but this blocked inside synchronized)");
    }
}

해결 방법: ReentrantLock

임계 영역에서 블로킹이 발생할 수 있다면 synchronized를 ReentrantLock으로 바꾸세요. 잠금 인터페이스는 가상 스레드에 적합하며 마운트 해제를 허용합니다.

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    static final ReentrantLock lock = new ReentrantLock();

    static void safe() {
        lock.lock();
        try {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::safe);
        t.join();
        System.out.println("Done without pinning");
    }
}

고정 진단하기

다음 옵션으로 실행하면 고정이 발생할 때마다 JVM이 스택 추적을 출력하도록 설정할 수 있습니다.

  • 전체 추적에는 -Djdk.tracePinnedThreads=full
  • 한 줄 표시에는 -Djdk.tracePinnedThreads=short

이를 통해 문제가 되는 synchronized 구간을 찾을 수 있습니다.

ThreadLocals의 비용 증가

ThreadLocal은 스레드별 상태를 저장합니다. 플랫폼 스레드가 몇 개뿐이라면 괜찮습니다. 하지만 각자 자신의 복사본을 보유하는 가상 스레드가 수백만 개라면 메모리 사용량이 크게 증가합니다.

라이브러리를 점검해 보세요. 캐싱, 형식 지정, 컨텍스트 객체에 ThreadLocals가 숨어 있는 경우가 많습니다.

ThreadLocal은 여전히 사용할 수 있습니다

ThreadLocal이 금지된 것은 아니지만 신중하게 사용하세요. 각 가상 스레드는 예상대로 고유한 값을 가집니다.

public class Main {
    static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(() -> {
            CONTEXT.set("request-42");
            System.out.println("Context: " + CONTEXT.get());
            CONTEXT.remove();
        });
        t.join();
    }
}

ScopedValue 우선 사용

Java는 수명이 제한된 불변 데이터를 공유하기 위한 더 가벼운 대안으로 ScopedValue를 도입했습니다. ThreadLocal의 스레드별 변경 가능한 저장 공간에 드는 비용을 피하며 구조적 동시성 모델에도 잘 맞습니다.

호출 체인을 따라 읽기 전용 컨텍스트만 전달하면 되는 경우에는 대신 이것을 사용하세요.

풀링하지 않기

또 다른 미묘한 위험 요소는 가상 스레드를 부족한 리소스로 간주해 풀링하는 것입니다. 이렇게 하면 작업 간 경합과 ThreadLocal 누수가 다시 발생합니다.

항상 작업마다 하나의 가상 스레드를 만들고 종료되도록 하세요.

확장성을 위한 점검 목록

가상 스레드를 사용해 운영 환경에 배포하기 전에 다음을 확인하세요.

  • 블로킹하는 synchronized를 ReentrantLock으로 바꾸세요
  • 테스트에서 jdk.tracePinnedThreads를 활성화하세요
  • ThreadLocal 사용을 최소화하고 ScopedValue를 우선 사용하세요
  • 가상 스레드를 절대 풀링하지 마세요

빠른 확인

블로킹하는 동기화 블록을 안전하게 대체할 방법을 찾으세요.

정리

주요 위험 요소를 배웠습니다.

  • 고정: synchronized 내부의 블로킹이 캐리어 스레드를 묶습니다. ReentrantLock으로 해결하세요
  • -Djdk.tracePinnedThreads로 진단하세요
  • ThreadLocal의 오버헤드는 스레드 수에 따라 증가하므로 ScopedValue를 우선 사용하세요
  • 가상 스레드를 절대 풀링하지 마세요

이로써 가상 스레드 과정이 끝났습니다.

자주 묻는 질문

“함정: 고정과 ThreadLocals” 강의는 무료인가요?

네 — “함정: 고정과 ThreadLocals” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“함정: 고정과 ThreadLocals”에서 뭘 배우나요?

운반 스레드를 블로킹하지 않습니다 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Java Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“함정: 고정과 ThreadLocals” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 가상 스레드란 무엇인가
  2. 가상 스레드 생성
  3. 플랫폼 스레드와 가상 스레드
  4. 함정: 고정과 ThreadLocals
← Java Academy(으)로 돌아가기