java - 唤醒线程而不会有被阻塞的风险

标签 java multithreading thread-sleep

我有一个无限期运行的工作线程,如果没有什么可做的,它就会 hibernate 一分钟。有时,另一段代码会产生一些工作并希望立即唤醒工作线程。

所以我做了这样的事情(代码仅供说明):

class Worker {
    public void run() {
        while (!shuttingDown()) {
           step();
        }
    }

    private synchronized void step() {
        if (hasWork()) {
            doIt();
        } else {
            wait(60_000);
        }
    }

    public synchronized wakeMeUpInside() {
        notify();
    }
}

我不喜欢的是必须进入监视器才能唤醒某些东西,这意味着通知线程可能会无缘无故地延迟。由于 native 同步的选择有限,我想我应该切换到 Condition,但它有 exactly the same problem :

An implementation may (and typically does) require that the current thread hold the lock associated with this Condition when this method is called.

最佳答案

这是一个基于信号量的解决方案:

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 秒才能完成工作。如果没有可用的,它最终会回到 run方法,它将立即将其发送回 step方法将再次等待。我这样做是因为我假设您有某种原因想要每 60 秒运行一次,即使没有工作。如果不是这种情况,请调用 aquire而且你会无限期地等待工作。

根据下面的评论,OP 只想运行一次。虽然您可以调用 drainPermits在这种情况下,更干净的解决方案就是使用 LockSupport像这样:

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);
    }
}

关于java - 唤醒线程而不会有被阻塞的风险,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42322324/

相关文章:

java - 从套接字接收到的消息数

python - Keras:处理线程和大型数据集

java - Thread.sleep() 的精确替代方案

java - 如何将 sleep 添加到我的 Retry TestNG 方法中?

android - Android 停止的前台服务稍后不会重新启动

java - JDK、JRE 中的内部类、嵌套类、本地类和匿名类示例

java - 检查一个 ArrayList 的元素是否存在于另一个

java - 为什么 java BufferedReader 会丢失输出

c++ - 为什么C++上的互斥锁会如此严重地影响多线程效率?

java - 令人困惑的 Java 同步方法、同步(this) 和同步类