[英]is there a 'block until condition becomes true' function in java?
我正在为服务器编写一个侦听器线程,目前我正在使用:
while (true){
try {
if (condition){
//do something
condition=false;
}
sleep(1000);
} catch (InterruptedException ex){
Logger.getLogger(server.class.getName()).log(Level.SEVERE, null, ex);
}
}
使用上面的代码,我遇到了 run 函数占用所有 cpu 时间循环的问题。 sleep 功能有效,但它似乎是临时修复,而不是解决方案。
是否有一些函数会阻塞,直到变量“条件”变为“真”? 或者是持续循环等待变量值改变的标准方法?
像这样的轮询绝对是最不受欢迎的解决方案。
我假设您有另一个线程可以执行某些操作以使条件成立。 有几种方法可以同步线程。 在您的情况下,最简单的方法是通过对象发出通知:
主线:
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 上同步,否则不允许调用foo.wait()
或foo.notify()
是有原因的。 这是因为避免丢失通知的唯一方法是使用互斥锁保护条件。 当它正确完成时,它看起来像这样:
消费者线程:
try {
synchronized(foo) {
while(! conditionIsTrue()) {
foo.wait();
}
doSomethingThatRequiresConditionToBeTrue();
}
} catch (InterruptedException e) {
handleInterruption();
}
生产者线程:
synchronized(foo) {
doSomethingThatMakesConditionTrue();
foo.notify();
}
改变条件的代码和检查条件的代码都在同一个对象上同步,消费者线程在等待之前显式地测试条件。 当条件已经为真时,消费者不可能错过通知并最终永远停留在wait()
调用中。
另请注意, wait()
处于循环中。 这是因为,在一般情况下,当消费者重新获取foo
锁并唤醒时,其他线程可能再次使条件为假。 即使在你的程序是不可能的,什么是可能的,在某些操作系统,是foo.wait()
返回即使foo.notify()
没有被调用。 这称为虚假唤醒,它被允许发生,因为它使等待/通知更容易在某些操作系统上实现。
由于没有人使用 CountDownLatch 发布解决方案。 关于什么:
public class Lockeable {
private final CountDownLatch countDownLatch = new CountDownLatch(1);
public void doAfterEvent(){
countDownLatch.await();
doSomething();
}
public void reportDetonatingEvent(){
countDownLatch.countDown();
}
}
与 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
代替内在同步有很多优点,但我更喜欢有一个显式的Condition
对象来表示条件(你可以有多个,这对于生产者-消费者这样的事情来说是一种很好的接触)。
另外,我不禁注意到您如何处理示例中的中断异常。 您可能不应该像这样使用异常,而是使用Thread.currentThread().interrupt
重置中断状态标志。
这是因为如果抛出异常,中断状态标志将被重置(它说“我不再记得被中断了,如果他们问,我将无法告诉其他人我一直在”)并且另一个进程可能依靠这个问题。 例如,其他东西已经基于此实施了中断策略……呸。 另一个例子可能是您的中断策略,而不是while(true)
可能已实现为while(!Thread.currentThread().isInterrupted()
(这也将使您的代码更......更适合社交)。
因此,总而言之,当您想使用Lock
时,使用Condition
大致相当于使用 wait/notify/notifyAll ,日志记录是邪恶的,而吞下InterruptedException
是顽皮的 ;)
您可以使用信号量。
当条件不满足时,另一个线程获取信号量。
您的线程将尝试使用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();
还可以利用CompletableFuture
s(自 Java 8 起):
final CompletableFuture<String> question = new CompletableFuture<>();
// from within the consumer thread:
final String answer = question.get(); // or: event.get(7500000, TimeUnit.YEARS)
// from within the producer thread:
question.complete("42");
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.