LockSupport — 기본 스레드 블로킹 프리미티브

LockSupport — 기본 스레드 블로킹 프리미티브

LockSupport는 락과 다른 동기화 클래스를 만드는 데 쓰는 기본 스레드 블로킹 프리미티브를 제공해요. park/unpark로 스레드를 효율적으로 블로킹·언블로킹하는 저수준 수단이에요.

출처: Java API Reference

본문

개념 이해하기

이 클래스는 사용하는 각 스레드에 퍼밋(permit) 을 연관시켜요 (Semaphore의 의미에서). park 호출은 퍼밋이 있으면 즉시 반환하며(그 과정에서 소비) 없으면 블로킹할 수 있어요. unpark 호출은 아직 없었다면 퍼밋을 "사용 가능하게" 만들어요. (Semaphore와 달리 퍼밋은 축적되지 않으며 최대 하나예요.)

public class LockSupport
extends Object
  • 신뢰할 수 있는 사용에는 volatile(또는 원자적) 변수로 언제 park/unpark할지 제어하는 게 필요해요. 메서드 호출 순서는 volatile 변수 접근에 대해 유지되지만, non-volatile 변수 접근에 대해서는 그렇지 않을 수 있어요.
  • park/unparkThread.suspend/resume의 문제를 겪지 않는 효율적인 블로킹 수단이에요 — 퍼밋 덕분에 parkunpark 사이의 경합이 liveness를 보존해요. 또한 park는 호출자 스레드가 인터럽트되면 반환하고, 타임아웃 변형이 지원돼요.
  • park는 "아무 이유 없이" 반환할 수도 있으므로 일반적으로 루프 안에서 조건을 재검사하며 호출해야 해요. 이 점에서 park는 "busy wait"의 최적화 역할로, 스피닝만큼 시간을 낭비하지 않지만 효과적이려면 unpark와 짝을 이뤄야 해요.

blocker 객체: 세 가지 park 형태 각각 blocker 객체 인자를 지원해요. 스레드가 블로킹되는 동안 이 객체가 기록되어 모니터링·진단 도구가 스레드가 블로킹된 이유를 식별할 수 있게 해요 (도구는 getBlocker(Thread)로 접근). 락 구현에서 blocker로 줄 정상 인자는 this예요.

활용 예 — FIFO 비재진입 락

class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();

    public void lock() {
        boolean wasInterrupted = false;
        waiters.add(Thread.currentThread());   // publish current thread for unparkers
        while (waiters.peek() != Thread.currentThread() ||
               !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted())          // ignore interrupts while waiting
                wasInterrupted = true;
        }
        waiters.remove();
        if (wasInterrupted)                    // ensure correct interrupt status on return
            Thread.currentThread().interrupt();
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }

    static {
        // Reduce the risk of "lost unpark" due to classloading
        Class<?> ensureLoaded = LockSupport.class;
    }
}

park/unpark 메서드

public static void unpark(Thread thread) — 아직 없었다면 주어진 스레드의 퍼밋을 사용 가능하게 만들어요. 스레드가 park에 블로킹돼 있으면 언블로킹되고, 아니면 다음 park 호출이 블로킹하지 않음이 보장돼요. 시작되지 않은 스레드엔 효과가 보장되지 않아요.

public static void park(Object blocker) / public static void park() — 퍼밋이 있으면 소비하고 즉시 반환, 없으면 unpark, 인터럽트, 또는 spurious 반환 중 하나가 일어날 때까지 대기 상태가 돼요. 어떤 것이 원인인지 보고하지 않으므로 호출자는 조건을 재검사해야 해요.

public static void parkNanos(Object blocker, long nanos) / parkNanos(long nanos) — 최대 지정 나노초까지 기다려요. 0 이하는 아무것도 안 해요.

public static void parkUntil(Object blocker, long deadline) / parkUntil(long deadline) — 데드라인(Epoch 이후 밀리초)까지 기다려요.

그 외

public static Object getBlocker(Thread t) — 아직 언블로킹되지 않은 가장 최근 park 호출에 공급된 blocker 객체를 반환하거나, 블로킹되지 않았으면 null. 일시적 스냅샷이에요.

  • NullPointerException — 인자가 null일 때

public static void setCurrentBlocker(Object blocker) — 현재 스레드의 getBlocker 호출이 반환할 객체를 설정해요. park(b)의 효과를 얻으려면 setCurrentBlocker(b); park(); setCurrentBlocker(null); 형태를 써요. (JDK 14+)

더 알아보기 (Learn more)