Java 避免实用程序中的等待/通知以挂起/恢复线程

Java 避免实用程序中的等待/通知以挂起/恢复线程,java,multithreading,wait,notify,Java,Multithreading,Wait,Notify,我正在实现以下Java接口,以允许暂停和恢复线程。我有一个使用wait()/notifyAll()的工作版本,但我想知道是否有更简单的方法(比如,在java.util.concurrent中使用一些漂亮的小部件) 是的,有,但通过架构 class Suspender { protected static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime(

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


是的,有,但通过架构

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;
  }
}
因此,它基本上是根据暂停/恢复调用来安排和取消runnable

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


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

这一实现可以很容易地建立在持有1许可证的基础上

  • pause()
    获取许可证(在获得许可证之前进行阻止)
  • resume()
    再次释放许可证
  • maybePause()
    获取许可并阻止,直到它获得许可。然后再次释放它
要在同一个挂起程序上处理多个暂停请求,可以使用另一个信号量作为更改暂停状态的权限,并在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()
方法根据暂停的布尔标志决定是否立即释放许可证。如果设置了,将向信号量添加一个许可证,并立即获取该许可证。如果未设置,暂停线程的数量将增加,信号量上的acquire将被阻塞

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


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

我认为这种方法存在一个问题——如果将pause()实现为sempahore.tryAcquire(),那么它可能是不幸运的,并在maybePause()的中间尝试它,结果是你没有在应该的时候进入暂停模式。谢谢…你有没有一个实现来说明(2信号量版本)是如何工作的?我今晚会写一个,没有时间。谢谢。不幸的是,我认为这件事也有问题。考虑序列:1)最初我们不处于暂停模式。2) 线程1调用maybePause(),后者调用token.release(),然后释放锁,但尚未访问token.acquire()。3) 线程2调用pause(),使我们处于暂停模式。4) 线程3调用maybePause()。这应该会阻塞,因为我们现在处于暂停模式。但是,它到达token.acquire(),并且有一个可用的许可证(从步骤2开始),因此立即退出该方法。我认为这不是问题。这只是pause()调用,用于停止已经通过pause检查的一个线程。线程是否应该暂停在同步块中进行检查,然后让它通过,或者等待它检查是否需要再次暂停。总是值得研究解决问题的不同方法,但是Suspender接口在我的用例中工作得非常好。我更感兴趣的是如何实现该接口,而不必求助于低级并发原语。
@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();
        }
    }
}