简体   繁体   English

唤醒线程而不冒被阻塞的风险

[英]Waking up a thread without risking to get blocked

I have a worker thread running indefinitely, which goes to sleep for one minute if there's nothing to do. 我有一个无限期运行的工作线程,如果无事可做,它将进入睡眠状态一分钟。 Sometimes, another piece of code produces some work and wants to wake the worker thread immediately. 有时,另一段代码会产生一些工作,并且想要立即唤醒工作线程。

So I did something like this (code for illustration only): 所以我做了这样的事情(仅用于说明的代码):

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

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

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

What I dislike is having to enter the monitor only for waking something up, which means that the notifying thread may be delayed for no good reason. 我不喜欢的只是进入监视器才能唤醒某些东西,这意味着通知线程可能没有充分的理由被延迟。 As the choices of native synchronization are limited, I thought I'd switch to Condition , but it has exactly the same problem : 由于本机同步的选择是有限的,我以为我会切换到Condition ,但是它有完全相同的问题

An implementation may (and typically does) require that the current thread hold the lock associated with this Condition when this method is called. 当调用此方法时,实现可能(并且通常确实)要求当前线程持有与此Condition相关联的锁。

Here's a semaphore based solution: 这是一个基于信号量的解决方案:

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

I'm not 100% sure this meets your needs. 我不确定100%符合您的需求。 A few things to note: 注意事项:

  • This will add one permit each time wakeMeUpInside is called. 每次调用wakeMeUpInside都会添加一个许可。 Thus if two threads wake up the Worker it will run doIt twice without blocking. 因此,如果两个线程唤醒了Worker ,它将在没有阻塞的情况下运行doIt两次。 You can extend the example to avoid that. 您可以扩展示例以避免这种情况。
  • This waits 60 seconds for work to do. 这需要等待60秒才能完成工作。 If none is available it'll end up back in the run method which will send it immediately back to the step method which will just wait again. 如果没有可用的方法,它将返回到run方法,该方法将立即将其发送回step方法,后者将再次等待。 I did this because I'm assuming you had some reason why you wanted to run every 60 seconds even if there's no work. 我这样做是因为我假设您有某种原因,即使您没有工作,您也希望每60秒运行一次。 If that's not the case just call aquire and you'll wait indefinitely for work. 如果不是这种情况,请致电aquire ,您将无限期地等待工作。

As per comments below the OP wants to run only once. 根据下面的注释,OP只能运行一次。 While you could call drainPermits in that case a cleaner solution is just to use a LockSupport like so: 在这种情况下,虽然可以调用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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM