【问题标题】:Waking up a thread without risking to get blocked唤醒线程而不冒被阻塞的风险
【发布时间】:2017-07-08 10:04:20
【问题描述】:

我有一个无限期运行的工作线程,如果无事可做,它会休眠一分钟。有时,另一段代码产生了一些工作并想立即唤醒工作线程。

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

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

当调用此方法时,实现可能(并且通常确实)要求当前线程持有与此条件关联的锁。

【问题讨论】:

  • 我想,我在做一些愚蠢的事情......实际上,只有 waitnotify 需要包含在同步块中。那里还有另一个不相关的同步,我把它混为一谈......
  • 我推荐BlockingQueue。这样你就可以忘记synchronizedwait/notify。您只需要在关闭时中断正在等待queue.poll() 的线程即可。
  • @Kayaman 我知道BlockingQueue,但我没有将工作发送到线程,我只是通知它。此外,所有工作(由多个线程产生)可能会或可能不会在一个步骤中执行。

标签: java multithreading thread-sleep


【解决方案1】:

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

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 下面的 cmets 只想运行一次。虽然在这种情况下您可以调用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);
    }
}

【讨论】:

  • 一个好主意,但是“它将运行doIt 两次而不会阻塞”真的让我很困扰。我想,我可以循环吃掉所有许可证。
  • 如果你希望它只运行一次,只需调用drainPermits
  • 但请不要删除Semophore
  • @maaartinus 当然。保留 Semaphore 解决方案,但添加了一个直接使用 LockSupport 来阻塞/解除阻塞线程的解决方案。
  • 完美!我敢肯定,这两种解决方案都会奏效,而且我学到了一些东西。顺便说一句,在比赛中不必要地拨打doIt 是无害的。实际上,即使没有通知,我也会每分钟调用一次,因为可能会有一些以前失败的工作在延迟后重试。但这是一个小细节......
猜你喜欢
  • 2011-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 2012-01-25
相关资源
最近更新 更多