program tip

자바에 '조건이 참이 될 때까지 차단'기능이 있습니까?

radiobox 2020. 11. 17. 07:59
반응형

자바에 '조건이 참이 될 때까지 차단'기능이 있습니까?


서버에 대한 리스너 스레드를 작성하고 있으며 현재 다음을 사용하고 있습니다.

while (true){
    try {
        if (condition){
            //do something
            condition=false;
        }
        sleep(1000);

    } catch (InterruptedException ex){
        Logger.getLogger(server.class.getName()).log(Level.SEVERE, null, ex);
    }
}

위의 코드를 사용하면 모든 CPU 시간 루프를 먹는 실행 기능에 문제가 있습니다. 수면 기능은 작동하지만 해결책이 아닌 임시 수정 인 것 같습니다.

변수 'condition'이 'true'가 될 때까지 차단하는 기능이 있습니까? 아니면 변수의 값이 변경 될 때까지 기다리는 표준 방법을 계속 반복하고 있습니까?


이와 같은 폴링은 확실히 가장 선호되지 않는 솔루션입니다.

조건을 true로 만들기 위해 무언가를 할 다른 스레드가 있다고 가정합니다. 스레드를 동기화하는 방법에는 여러 가지가 있습니다. 귀하의 경우 가장 쉬운 방법은 객체를 통한 알림입니다.

주요 스레드 :

synchronized(syncObject) {
    try {
        // Calling wait() will block this thread until another thread
        // calls notify() on the object.
        syncObject.wait();
    } catch (InterruptedException e) {
        // Happens if someone interrupts your thread.
    }
}

다른 스레드 :

// Do something
// If the condition is true, do the following:
synchronized(syncObject) {
    syncObject.notify();
}

syncObject그 자체는 간단 할 수 있습니다 Object.

스레드 간 통신에는 다른 많은 방법이 있지만 사용할 방법은 정확히 수행하는 작업에 따라 다릅니다.


EboMike의 대답Toby의 대답 은 모두 올바른 길을 가고 있지만 둘 다 치명적인 결함이 있습니다. 이 결함을 분실 알림 이라고 합니다.

문제는 스레드가를 호출하는 경우 foo.notify()다른 스레드가 이미 foo.wait()호출 에서 잠자기 상태가 아니면 아무것도 수행하지 않는다는 것 입니다. 개체 foo는 알림을 받았음을 기억하지 않습니다.

당신이 전화를 할 수 없습니다 이유있다 foo.wait()또는 foo.notify()스레드가 foo는에 동기화되지 않은 경우는. 알림 손실을 방지하는 유일한 방법은 뮤텍스로 조건을 보호하는 것입니다. 올바르게 완료되면 다음과 같이 보입니다.

소비자 스레드 :

try {
    synchronized(foo) {
        while(! conditionIsTrue()) {
            foo.wait();
        }
        doSomethingThatRequiresConditionToBeTrue();
    }
} catch (InterruptedException e) {
    handleInterruption();
}

생산자 스레드 :

synchronized(foo) {
    doSomethingThatMakesConditionTrue();
    foo.notify();
}

조건을 변경하는 코드와 조건을 확인하는 코드는 모두 동일한 개체에서 동기화되며 소비자 스레드는 대기 전에 조건을 명시 적으로 테스트합니다. 소비자가 알림을 놓치고 wait()조건이 이미 참일 때 통화 에서 영원히 멈출 수있는 방법은 없습니다 .

또한 wait()루프에 있습니다. 일반적인 경우 소비자가 foo잠금을 다시 획득 하고 깨어날 때 다른 스레드가 조건을 다시 거짓으로 만들었을 수 있기 때문입니다. 그것이 당신의 프로그램 에서 가능하지 않더라도 , 일부 운영 체제에서 가능한 것은 호출되지 않은 foo.wait()경우에도 리턴하는 것 foo.notify()입니다. 이를 스퓨리어스 웨이크 업 이라고하며 특정 운영 체제에서 대기 / 알림을보다 쉽게 ​​구현할 수 있기 때문에 발생할 수 있습니다.


EboMike의 답변과 유사하게 wait / notify / notifyAll과 유사한 메커니즘을 사용할 수 있지만 Lock.

예를 들면

public void doSomething() throws InterruptedException {
    lock.lock();
    try {
        condition.await(); // releases lock and waits until doSomethingElse is called
    } finally {
        lock.unlock();
    }
}

public void doSomethingElse() {
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

다른 스레드 (이 경우를 호출 doSomethingElse) 에 의해 통지되는 조건을 기다리는 곳에서 , 그 시점에서 첫 번째 스레드가 계속됩니다.

Lock내장 동기화보다 s를 사용하면 많은 이점이 있지만 Condition조건을 나타내는 명시적인 객체를 선호 합니다 (생산자-소비자 같은 것에 대해 좋은 터치를 하나 이상 가질 수 있음).

또한 귀하의 예에서 중단 된 예외를 처리하는 방법을 알아 차릴 수는 없습니다. 이와 같은 예외를 사용해서는 안되며 대신을 사용하여 인터럽트 상태 플래그를 재설정하십시오 Thread.currentThrad().interrupt.

예외가 발생하면 인터럽트 상태 플래그가 재설정되고 ( " 더 이상 인터럽트 된 것을 기억하지 못합니다. 다른 사람에게 물어 보면 내가 있었다고 말할 수 없습니다. "라고 말하고 ) 다른 프로세스가 의존 할 수 있기 때문입니다. 이 질문. 예를 들어 다른 것이 이것을 기반으로 중단 정책을 구현 한 것입니다. 휴. 추가 예는 당신이 중단 정책이라고 할 while(true)수 있습니다. 오히려 그것은 while(!Thread.currentThread().isInterrupted()(당신의 코드가 사회적으로 더 사려 깊도록 만들 것입니다).

따라서 요약하면 using Condition은를 사용하고 싶을 때 wait / notify / notifyAll을 사용하는 것과 거의 동일 Lock하며 로깅은 악하고 삼키는 InterruptedException것은 장난 스럽습니다.)


아무도 CountDownLatch로 솔루션을 게시하지 않았습니다. 는 어때:

public class Lockeable {
    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    public void doAfterEvent(){
        countDownLatch.await();
        doSomething();
    }

    public void reportDetonatingEvent(){
        countDownLatch.countDown();
    }
}

세마포어를 사용할 수 있습니다 .

조건이 충족되지 않는 동안 다른 스레드는 세마포어를 획득합니다.
스레드는 그것을 얻기 위해 노력할 것이다 acquireUninterruptibly()
이나 tryAcquire(int permits, long timeout, TimeUnit unit)와 차단됩니다.

조건이 충족되면 세마포어도 해제되고 스레드가이를 획득합니다.

또한 사용하여 시도 할 수 SynchronousQueue또는를 CountDownLatch.


잠금없는 솔루션 (?)

나는 같은 문제가 있었지만 잠금을 사용하지 않는 솔루션을 원했습니다.

문제 : 대기열에서 소비하는 스레드가 최대 하나입니다. 여러 생산자 스레드가 지속적으로 대기열에 삽입되고 대기중인 경우 소비자에게 알려야합니다. 큐는 잠금이 없으므로 알림에 잠금을 사용하면 생산자 스레드에서 불필요한 차단이 발생합니다. 각 생산자 스레드는 대기중인 소비자에게 알리기 전에 잠금을 획득해야합니다. LockSupport을 사용하여 잠금없는 솔루션을 생각해 냈다고 생각 AtomicReferenceFieldUpdater합니다. JDK 내에 잠금없는 장벽이 있으면 찾을 수 없습니다. 둘 다 내가 찾은 것에서 내부적으로 잠금 CyclicBarrierCoundDownLatch사용합니다.

이것은 약간 축약 된 코드입니다. 명확히하기 위해이 코드는 한 번에 하나의 스레드 대기 하도록 허용 합니다. 여러 소유자를 저장하기 위해 특정 유형의 원자 컬렉션을 사용하여 여러 대기자 / 소비자를 허용하도록 수정할 수 있습니다 (일 수 있음 ConcurrentMap).

이 코드를 사용했는데 작동하는 것 같습니다. 나는 그것을 광범위하게 테스트하지 않았습니다. LockSupport사용하기 전에 설명서를 읽어 보시기 바랍니다 .

/* I release this code into the public domain.
 * http://unlicense.org/UNLICENSE
 */

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;

/**
 * A simple barrier for awaiting a signal.
 * Only one thread at a time may await the signal.
 */
public class SignalBarrier {
    /**
     * The Thread that is currently awaiting the signal.
     * !!! Don't call this directly !!!
     */
    @SuppressWarnings("unused")
    private volatile Thread _owner;

    /** Used to update the owner atomically */
    private static final AtomicReferenceFieldUpdater<SignalBarrier, Thread> ownerAccess =
        AtomicReferenceFieldUpdater.newUpdater(SignalBarrier.class, Thread.class, "_owner");

    /** Create a new SignalBarrier without an owner. */
    public SignalBarrier() {
        _owner = null;
    }

    /**
     * Signal the owner that the barrier is ready.
     * This has no effect if the SignalBarrer is unowned.
     */
    public void signal() {
        // Remove the current owner of this barrier.
        Thread t = ownerAccess.getAndSet(this, null);

        // If the owner wasn't null, unpark it.
        if (t != null) {
            LockSupport.unpark(t);
        }
    }

    /**
     * Claim the SignalBarrier and block until signaled.
     *
     * @throws IllegalStateException If the SignalBarrier already has an owner.
     * @throws InterruptedException If the thread is interrupted while waiting.
     */
    public void await() throws InterruptedException {
        // Get the thread that would like to await the signal.
        Thread t = Thread.currentThread();

        // If a thread is attempting to await, the current owner should be null.
        if (!ownerAccess.compareAndSet(this, null, t)) {
            throw new IllegalStateException("A second thread tried to acquire a signal barrier that is already owned.");
        }

        // The current thread has taken ownership of this barrier.
        // Park the current thread until the signal. Record this
        // signal barrier as the 'blocker'.
        LockSupport.park(this);
        // If a thread has called #signal() the owner should already be null.
        // However the documentation for LockSupport.unpark makes it clear that
        // threads can wake up for absolutely no reason. Do a compare and set
        // to make sure we don't wipe out a new owner, keeping in mind that only
        // thread should be awaiting at any given moment!
        ownerAccess.compareAndSet(this, t, null);

        // Check to see if we've been unparked because of a thread interrupt.
        if (t.isInterrupted())
            throw new InterruptedException();
    }

    /**
     * Claim the SignalBarrier and block until signaled or the timeout expires.
     *
     * @throws IllegalStateException If the SignalBarrier already has an owner.
     * @throws InterruptedException If the thread is interrupted while waiting.
     *
     * @param timeout The timeout duration in nanoseconds.
     * @return The timeout minus the number of nanoseconds that passed while waiting.
     */
    public long awaitNanos(long timeout) throws InterruptedException {
        if (timeout <= 0)
            return 0;
        // Get the thread that would like to await the signal.
        Thread t = Thread.currentThread();

        // If a thread is attempting to await, the current owner should be null.
        if (!ownerAccess.compareAndSet(this, null, t)) {
            throw new IllegalStateException("A second thread tried to acquire a signal barrier is already owned.");
        }

        // The current thread owns this barrier.
        // Park the current thread until the signal. Record this
        // signal barrier as the 'blocker'.
        // Time the park.
        long start = System.nanoTime();
        LockSupport.parkNanos(this, timeout);
        ownerAccess.compareAndSet(this, t, null);
        long stop = System.nanoTime();

        // Check to see if we've been unparked because of a thread interrupt.
        if (t.isInterrupted())
            throw new InterruptedException();

        // Return the number of nanoseconds left in the timeout after what we
        // just waited.
        return Math.max(timeout - stop + start, 0L);
    }
}

모호한 사용 예를 제공하기 위해 james large의 예를 채택하겠습니다.

SignalBarrier barrier = new SignalBarrier();

소비자 스레드 ( 복수가 아닌 단수! ) :

try {
    while(!conditionIsTrue()) {
        barrier.await();
    }
    doSomethingThatRequiresConditionToBeTrue();
} catch (InterruptedException e) {
    handleInterruption();
}

생산자 스레드 :

doSomethingThatMakesConditionTrue();
barrier.signal();

참고 URL : https://stackoverflow.com/questions/5999100/is-there-a-block-until-condition-becomes-true-function-in-java

반응형