Condition — 조건 변수 인터페이스

Condition — 조건 변수 인터페이스

ConditionObject 모니터 메서드(wait, notify, notifyAll)를 별개의 객체로 분리해, 임의의 Lock 구현과 결합해 객체당 여러 개의 wait-set을 갖는 효과를 주는 인터페이스예요. Locksynchronized를 대체하듯, ConditionObject 모니터 메서드를 대체해요.

출처: Java API Reference

본문

개념 이해하기

Condition(조건 큐 또는 조건 변수라고도 함)은 한 스레드가 어떤 상태 조건이 이제 참일 수 있음을 다른 스레드가 알릴 때까지 실행을 정지("wait")하게 해주는 수단이에요. 공유 상태 정보에 대한 접근은 여러 스레드에서 일어나므로 보호돼야 하며, 어떤 형태의 락이 조건과 연관돼요.

조건 대기가 제공하는 핵심 속성Object.wait처럼 연관된 락을 원자적으로 해제하고 현재 스레드를 정지시키는 것이에요.

Condition 인스턴스는 본질적으로 락에 묶여 있어요. 특정 Lock 인스턴스에 대한 Condition을 얻으려면 newCondition() 메서드를 쓰세요.

활용 예 — 유한 버퍼

take 시도가 빈 버퍼에서 일어나면 항목이 생길 때까지, put 시도가 꽉 찬 버퍼에서 일어나면 공간이 생길 때까지 블로킹하는 것을 서로 다른 wait-set으로 분리:

class BoundedBuffer<E> {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(E x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            E x = (E) items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

구현 고려 사항

  • 대기 중 "spurious wakeup"(거짓 깨움)이 발생할 수 있어요. 그러므로 Condition은 항상 루프 안에서 대기 상태 술어를 검사하며 기다려야 해요.
  • 조건 대기의 세 가지 형태(인터럽트 가능, 비인터럽트, 시간 제한)는 구현에 따라 동작이 다를 수 있어요. 구현은 각 대기 메서드가 제공하는 의미론과 보장을 명확히 문서화해야 해요.
  • 인자에 null을 넘기면 (명시된 경우를 제외하고) NullPointerException이 던져져요.

대기 메서드

void await() throws InterruptedException — 신호를 받거나 인터럽트될 때까지 현재 스레드를 기다리게 해요. 연관된 락은 원자적으로 해제되고, signal()/signalAll()에 선택되거나 인터럽트되거나 spurious wakeup이 일어날 때까지 대기 상태가 돼요. 반환 전에 반드시 락을 재획득하며, 반환 시 그 락을 보유함이 보장돼요.

  • InterruptedException

void awaitUninterruptibly() — 신호를 받을 때까지 기다려요. 인터럽트되어도 계속 기다리고, 반환 시 인터럽트 상태가 여전히 설정돼 있어요.

long awaitNanos(long nanosTimeout) throws InterruptedException — 신호, 인터럽트, 또는 지정 대기 시간이 지날 때까지 기다려요. 남은 나노초 수의 추정치(또는 타임아웃 시 0 이하)를 반환해, 재대기 여부·시간을 결정하는 데 쓸 수 있어요.

boolean await(long time, TimeUnit unit) throws InterruptedExceptionawaitNanos(unit.toNanos(time)) > 0과 동등. 시간이 확실히 지나면 false, 아니면 true.

boolean awaitUntil(Date deadline) throws InterruptedException — 신호, 인터럽트, 또는 지정 데드라인이 지날 때까지 기다려요. 데드라인이 지나면 false.

신호 메서드

void signal() — 대기 중인 한 스레드를 깨워요. 깨어난 스레드는 await에서 반환되기 전에 락을 재획득해야 해요. 구현은 보통 호출 시 현재 스레드가 연관 락을 보유하길 요구하며, 그렇지 않으면 IllegalMonitorStateException을 던져요.

void signalAll() — 대기 중인 모든 스레드를 깨워요. 각 스레드는 반환 전에 락을 재획득해야 해요.

더 알아보기 (Learn more)