자바에 '조건이 참이 될 때까지 차단'기능이 있습니까?
서버에 대한 리스너 스레드를 작성하고 있으며 현재 다음을 사용하고 있습니다.
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 내에 잠금없는 장벽이 있으면 찾을 수 없습니다. 둘 다 내가 찾은 것에서 내부적으로 잠금 CyclicBarrier
을 CoundDownLatch
사용합니다.
이것은 약간 축약 된 코드입니다. 명확히하기 위해이 코드는 한 번에 하나의 스레드 만 대기 하도록 허용 합니다. 여러 소유자를 저장하기 위해 특정 유형의 원자 컬렉션을 사용하여 여러 대기자 / 소비자를 허용하도록 수정할 수 있습니다 (일 수 있음 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();
'program tip' 카테고리의 다른 글
PHP에서 call_user_func_array로 생성자를 호출하는 방법 (0) | 2020.11.17 |
---|---|
MySQL의 열 이름 "order"에 대한 대안 (0) | 2020.11.17 |
결과가 null 일 때 PL / SQL에서 변수를 선택하는 방법은 무엇입니까? (0) | 2020.11.17 |
EnumSet은 실제로 무엇을 의미합니까? (0) | 2020.11.17 |
JavaScript를 사용하여 한 번에 요소의 여러 속성 설정 (0) | 2020.11.17 |