唤醒线程而不会有被阻止的风险

时间:2017-02-19 00:38:12

标签: java multithreading thread-sleep

我有一个无限期运行的工作线程,如果没有任何关系,它会进入睡眠状态一分钟。有时,另一段代码会产生一些工作,并希望立即唤醒工作线程。

所以我做了类似的事情(仅用于说明的代码):

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

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

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

我不喜欢进入显示器只是为了唤醒某些东西,这意味着通知线程可能会被推迟没有充分理由。由于原生同步的选择有限,我想我会切换到Condition,但它有exactly the same problem

  

当调用此方法时,实现可能(通常会)要求当前线程保持与此Condition关联的锁。

1 个答案:

答案 0 :(得分:2)

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

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