Java 唤醒线程而不冒被阻塞的风险
我有一个工作线程无限期地运行,如果无事可做,它将休眠一分钟。有时,另一段代码会产生一些工作,并希望立即唤醒工作线程 所以我做了类似的事情(代码仅用于说明): 我不喜欢的是只有进入监视器才能唤醒某些东西,这意味着通知线程可能会无缘无故地延迟。由于本机同步的选择有限,我想我应该切换到Java 唤醒线程而不冒被阻塞的风险,java,multithreading,thread-sleep,Java,Multithreading,Thread Sleep,我有一个工作线程无限期地运行,如果无事可做,它将休眠一分钟。有时,另一段代码会产生一些工作,并希望立即唤醒工作线程 所以我做了类似的事情(代码仅用于说明): 我不喜欢的是只有进入监视器才能唤醒某些东西,这意味着通知线程可能会无缘无故地延迟。由于本机同步的选择有限,我想我应该切换到条件,但它有: 当调用此方法时,实现可能(并且通常确实)要求当前线程持有与此条件相关联的锁 下面是一个基于信号量的解决方案: class Worker { // If 0 there's no work avai
条件
,但它有:
当调用此方法时,实现可能(并且通常确实)要求当前线程持有与此条件相关联的锁
下面是一个基于信号量的解决方案:
class Worker {
// If 0 there's no work available
private workAvailableSem = new Semaphore(0);
public void run() {
while (!shuttingDown()) {
step();
}
}
private synchronized void step() {
// Try to obtain a permit waiting up to 60 seconds to get one
boolean hasWork = workAvailableSem.tryAquire(1, TimeUnit.MINUTES);
if (hasWork) {
doIt();
}
}
public wakeMeUpInside() {
workAvailableSem.release(1);
}
}
我不是100%确定这是否满足你的需要。需要注意的几点:
- 这将在每次调用
时添加一个许可证。因此,如果两个线程唤醒wakeMeUpInside
,它将运行Worker
两次而不阻塞。您可以扩展示例以避免这种情况doIt
- 这需要等待60秒才能完成工作。如果没有可用的,它将返回
方法,该方法将立即将其发送回运行
方法,该方法将再次等待。我这样做是因为我假设你有一些理由,为什么你想每60秒跑一次,即使没有工作。如果不是这样的话,只需调用步骤
,您将无限期地等待工作aquire
排水许可证
,但在这种情况下,更干净的解决方案只是使用锁支持
,如下所示:
class Worker {
// We need a reference to the thread to wake it
private Thread workerThread = null;
// Is there work available
AtomicBoolean workAvailable = new AtomicBoolean(false);
public void run() {
workerThread = Thread.currentThread();
while (!shuttingDown()) {
step();
}
}
private synchronized void step() {
// Wait until work is available or 60 seconds have passed
ThreadSupport.parkNanos(TimeUnit.MINUTES.toNanos(1));
if (workAvailable.getAndSet(false)) {
doIt();
}
}
public wakeMeUpInside() {
// NOTE: potential race here depending on desired semantics.
// For example, if doIt() will do all work we don't want to
// set workAvailable to true if the doIt loop is running.
// There are ways to work around this but the desired
// semantics need to be specified.
workAvailable.set(true);
ThreadSupport.unpark(workerThread);
}
}
下面是一个基于信号量的解决方案:
class Worker {
// If 0 there's no work available
private workAvailableSem = new Semaphore(0);
public void run() {
while (!shuttingDown()) {
step();
}
}
private synchronized void step() {
// Try to obtain a permit waiting up to 60 seconds to get one
boolean hasWork = workAvailableSem.tryAquire(1, TimeUnit.MINUTES);
if (hasWork) {
doIt();
}
}
public wakeMeUpInside() {
workAvailableSem.release(1);
}
}
我不是100%确定这是否满足你的需要。需要注意的几点:
- 这将在每次调用
时添加一个许可证。因此,如果两个线程唤醒wakeMeUpInside
,它将运行Worker
两次而不阻塞。您可以扩展示例以避免这种情况doIt
- 这需要等待60秒才能完成工作。如果没有可用的,它将返回
方法,该方法将立即将其发送回运行
方法,该方法将再次等待。我这样做是因为我假设你有一些理由,为什么你想每60秒跑一次,即使没有工作。如果不是这样的话,只需调用步骤
,您将无限期地等待工作aquire
排水许可证
,但在这种情况下,更干净的解决方案只是使用锁支持
,如下所示:
class Worker {
// We need a reference to the thread to wake it
private Thread workerThread = null;
// Is there work available
AtomicBoolean workAvailable = new AtomicBoolean(false);
public void run() {
workerThread = Thread.currentThread();
while (!shuttingDown()) {
step();
}
}
private synchronized void step() {
// Wait until work is available or 60 seconds have passed
ThreadSupport.parkNanos(TimeUnit.MINUTES.toNanos(1));
if (workAvailable.getAndSet(false)) {
doIt();
}
}
public wakeMeUpInside() {
// NOTE: potential race here depending on desired semantics.
// For example, if doIt() will do all work we don't want to
// set workAvailable to true if the doIt loop is running.
// There are ways to work around this but the desired
// semantics need to be specified.
workAvailable.set(true);
ThreadSupport.unpark(workerThread);
}
}
我猜,我在做一些愚蠢的事。。。实际上,只有
wait
和notify
需要包含在同步块中。这里还有另一个不相关的同步,我把它混为一谈了……我建议改为使用BlockingQueue
。这样您就可以忘记已同步
和等待/通知
。关闭时,您只需中断正在等待队列.poll()的线程。@Kayaman我知道阻塞队列
,但我不会将工作发送给线程,我只是通知它。而且所有的工作(由多个线程产生)可能在一个步骤中执行,也可能不在一个步骤中执行。。。实际上,只有wait
和notify
需要包含在同步块中。这里还有另一个不相关的同步,我把它混为一谈了……我建议改为使用BlockingQueue
。这样您就可以忘记已同步
和等待/通知
。关闭时,您只需中断正在等待队列.poll()的线程。@Kayaman我知道阻塞队列
,但我不会将工作发送给线程,我只是通知它。另外,所有工作(由多个线程产生)可能在一个步骤中执行,也可能不在一个步骤中执行。这是一个好主意,但“它将在不阻塞的情况下运行doIt
两次”真的让我困扰。我想,我可以在一个循环中吃掉所有的许可证。如果你想让它只运行一次,只需调用drainPermits
,但请不要删除符号
@maaartinus当然。保留了信号量解决方案,但添加了一个直接使用LockSupport阻止/取消阻止线程的解决方案。太棒了!我相信,两种解决方案都会奏效,我学到了一些东西。顺便说一句,在比赛中不必要地调用doIt
是无害的。事实上,我每分钟都会打电话给它,即使没有通知,因为可能有一些以前失败的工作要在延迟后重试。但这是一个小细节…一个好主意,但“它将在不阻塞的情况下运行doIt
两次”真的让我烦恼。我想,我可以在一个循环中吃掉所有的许可证。如果你想让它只运行一次,只需调用drainPermits
,但请不要删除符号
@maaartinus当然。保留了信号量解决方案,但添加了一个直接使用LockSupport阻止/取消阻止线程的解决方案。太棒了!我相信,两种解决方案都会奏效,我学到了一些东西。顺便说一句,在比赛中不必要地调用doIt
是无害的。事实上,我每分钟都会打电话给它,即使没有通知,因为可能有一些以前失败的工作要在延迟后重试。但这只是一个小细节。。。