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
    ,您将无限期地等待工作
根据下面的评论,OP只想运行一次。虽然您可以调用
排水许可证
,但在这种情况下,更干净的解决方案只是使用
锁支持
,如下所示:

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
    ,您将无限期地等待工作
根据下面的评论,OP只想运行一次。虽然您可以调用
排水许可证
,但在这种情况下,更干净的解决方案只是使用
锁支持
,如下所示:

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
是无害的。事实上,我每分钟都会打电话给它,即使没有通知,因为可能有一些以前失败的工作要在延迟后重试。但这只是一个小细节。。。