【问题标题】:Avoiding wait/notify in a utility to suspend/resume threads避免在实用程序中等待/通知以挂起/恢复线程
【发布时间】:2013-12-11 10:09:27
【问题描述】:

我正在实现以下 Java 接口以允许暂停和恢复线程。我有一个使用 wait()/notifyAll() 的工作版本,但我想知道是否有更简单的方法(例如,在 java.util.concurrent 中使用一些漂亮的小部件)?

public interface Suspender {

    /**
     * Go into pause mode: any threads which subsequently call maybePause()
     * will block. Calling pause() if already in pause mode has no effect.
     */
    void pause();

    /**
     * Leave pause mode: any threads which call maybePause() will not block,
     * and any currently paused threads will be unblocked. Calling resume()
     * if not in pause mode has no effect.
     */
    void resume();

    /**
     * If the Suspender is in pause mode, block, and only resume execution
     * when the Suspender is resumed. Otherwise, do nothing.
     */
    void maybePause();

}

【问题讨论】:

    标签: java multithreading wait notify


    【解决方案1】:

    是的,但通过架构。

    class Suspender {
    
      protected static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
      protected final Runnable task;
      protected final long period;
      protected final TimeUnit unit;
      private Future t;
    
      public Suspender(final Runnable task, final long initialDelay, final long period, final TimeUnit unit) {
        this.task = task;
        this.period = period;
        this.unit = unit;
        t = executor.scheduleAtFixedRate(task, initialDelay, period, unit);
      }
    
      public boolean pause() {
        if (t == null) {
          return false;
        }
        if (t.cancel(true)) {
          t = null;
          return true;
        }
        return false;
      }
    
      public boolean resume() {
        if (t == null) {
          t = executor.scheduleAtFixedRate(task, 0, period, unit);
          return true;
        }
        return false;
      }
    }
    

    所以它的作用基本上是根据您的暂停/恢复调用来安排和取消可运行对象。

    显然不会在任务中暂停线程,这是一件好事,因为如果您可以在执行期间暂停线程,则可能会导致各种问题。这从未释放的锁开始,但也包括半开的网络连接、半写的文件等等。

    所以当你有一个线程有一个任务时,不要暂停它。只有当您有一个执行重复任务的线程(因此是 ScheduledExecutorService)时,您才能跳过对该任务的进一步调用。然后,您的线程可以通过在可以合理地暂停/取消的代码状态期间查询Thread.interrupted() 标志,在内部决定是否可以在此时暂停。

    【讨论】:

    • 看一个问题的不同方法总是值得的,但是 Suspender 界面非常适合我的用例。我更感兴趣的是在不诉诸低级并发原语的情况下实现该接口的方法。
    【解决方案2】:

    可以轻松地在持有 1 个许可证的 java.uitl.concurrent.Semaphore 之上构建此实现。

    • pause() 获得许可(阻止直到获得许可)
    • resume() 再次释放许可。
    • maybePause() 获得许可并阻止,直到获得许可。然后再次释放它。

    要在同一个 Suspender 上处理多个暂停请求,您可以使用另一个信号量作为更改暂停状态的权限,并在 pause 和 resume 方法中使用它。

    编辑

    好吧,当我开始写作时,我意识到它并不像我想象的那么简单。尽管如此,我还是设法使用(甚至只是一个)信号量编写了一个实现:

    @ThreadSafe
    public class SuspenderImpl implements Suspender {
    
        @GuardedBy("stateLock")
        private boolean paused;
        @GuardedBy("stateLock")
        private int pausedThreads;
        private final Semaphore token = new Semaphore(0);
        private final Object stateLock = new Object();
    
        @Override
        public void pause() {
            synchronized (stateLock) {
                paused = true;
            }
        }
    
        @Override
        public void resume() {
            synchronized (stateLock) {
                paused = false;
                token.release(pausedThreads);
                pausedThreads = 0;
            }
        }
    
        @Override
        public void maybePause() {
            synchronized (stateLock) {
                if (paused) {
                    pausedThreads++;
                } else {
                    token.release();
                }
            }
            try {
                token.acquire();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    

    基本想法与我原来的想法有很大不同。在此实现中,maybePause() 方法根据暂停的布尔标志决定是否立即释放许可。如果设置,则将许可证添加到信号量并立即获取。如果未设置,则暂停线程的数量会增加,并且信号量上的获取将阻塞。

    pause() 方法只是设置标志。 resume() 方法将标志设置为 false,释放等于暂停线程数的许可并将计数设置为零。

    所有可变状态都由内部锁保护。

    【讨论】:

    • 我认为这种方法存在问题——如果你将 pause() 实现为 sempahore.tryAcquire(),那么它可能是不走运的,并在 mayPause() 的中间尝试它,使用结果是你最终没有进入暂停模式。
    • 谢谢...你有一个实现来展示它(2个信号量版本)是如何工作的吗?
    • 今晚我会尝试写一篇,没有时间atm。
    • 谢谢。不幸的是,我认为这也有问题。考虑以下顺序: 1) 最初我们不处于暂停模式。 2)线程1调用maybePause(),它调用token.release(),然后释放锁,但还没有到达token.acquire()。 3) 线程 2 调用 pause(),将我们置于暂停模式。 4) 线程 3 调用 MaybePause()。这应该会阻塞,因为我们现在处于暂停模式。但是,它到达了 token.acquire(),并且有一个可用的许可(从步骤 #2 开始),因此立即退出该方法。
    • 我认为这不是问题。这只是为了停止已经通过暂停检查的那个线程而缺少的 pause() 调用。在同步块中检查线程是否应该暂停,然后我们让它通过或等待它检查它是否需要再次暂停。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-23
    • 2021-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多