【问题标题】:Writing a comfortable pause/stop method for a Thread为线程编写一个舒适的暂停/停止方法
【发布时间】:2018-09-05 18:47:50
【问题描述】:

对于 Java 中的 Thread 子类,我正在尝试编写一个方法 checkPauseAndStop(),其目的是成为一个不错的单行代码,我可以在 run() 方法中定期调用以检查是否有暂停/停止请求并采取相应的行动。

虽然功能齐全,但停止线程是通过 InterruptedException 处理的,这会强制 run()-code 处于 try-catch 中并且感觉容易出错
(例如 Thread.sleep() 不会再告诉你它可能会抛出这样的异常)。

有没有什么好的方法可以在方法内部停止线程而不妨碍 run() 方法?

代码示例:

public class SuspendableThread extends Thread
{
   private Semaphore lock = new Semaphore(1);
   public void checkPauseRequest() throws InterruptedException
    {
        if (isInterrupted())
            throw new InterruptedException();

        lock.acquire();
        lock.release();

    }

    @Override
    public void run()
    {
        try
        {
            while (true)
            {
                // Do_stuff
                checkPauseRequest();
            }

        }
        catch (InterruptedException e)
        {
            return;
        }
    }

当然,在这个特定的例子中,它根本没有必要。为了获得更好的效果,假设我们有 50 个或更多连续的块

// Do_stuff
checkPauseRequest();

而不仅仅是一个。

【问题讨论】:

  • lock 需要被其他线程访问,这样它才能acquire(),所以当它acquire()s 时,这个线程会暂停。一个简单的 sharedAtomicBoolean shouldStop 可能足以返回而无需使用 InterruptedException。无论哪种方式,您仍然必须决定 checkPauseRequest() 应该放在循环中的哪个位置。无论是 1 次还是多次,都可以在某个工作单元之后暂停/停止。
  • 考虑使用Semaphore 而不是Lock。原因是,principle of least astonishment。当其他程序员看到Lock 时,他们通常希望它用于互斥;但是当他们看到Semaphore 时,他们希望它用于线程之间的信号传递。如果您使用Lock 发出信号,可能会给其他程序员留下您的编码风格“怪异”的印象,并且可能不值得信赖。
  • @AndrewS 我并没有真正遵循您的论点。除了作为Semaphore 的替代品之外,AtomicBoolean 在这里有何帮助?
  • AtomicBoolean 是一个能够优雅地退出线程的示例,而不是捕获 InterreuptedException 并从捕获中返回。 Semaphonre 仍然可以暂停线程。
  • @AndrewS 但是,我没有看到,除了使用异常之外,我还可以向后跳转多个作用域(除了为每个作用域调用 run 一次以退出)

标签: java multithreading java-threads


【解决方案1】:

我将使用ScheduledExecutorService(您可以使用Executors.newSingleThreadScheduledExecutor 创建)和enum RunningState { RUNNING, PAUSED, STOPPED } 来构建它。

虽然状态为RUNNING,但我们会继续调度延迟任务(使用ScheduledExecutorService.schedule)。每个任务检查我们是否仍然是RUNNING,如果是,则将单个循环迭代排入队列(使用ExecutorService.submitExecutor.execute)。然后它为下一次迭代安排一个新的延迟任务(与 Timer 不同,您可能会使用它来代替,但会牺牲另一个线程)。

如果状态为PAUSED,我们会继续调度延迟的任务,以继续检查是否转换回RUNNING 状态。但我们不会为实际的循环迭代安排工作项。

如果状态为STOPPED,那么我们将停止调度任何延迟的任务。

状态变量可以存储在一个简单的volatile 字段中,它可以是静态的(如果只有这些东西之一)或者可以包裹在一个代表“线程”的对象中(不是实际上不再是一个线程,但从调用者的角度来看可能类似于一个线程)。

【讨论】:

  • 这是一种有趣的方法。假设我没有几行循环,而是一长串不同的任务,我在这些任务上循环。要将其转换为与调度程序一起使用,我不是必须将每个工作单元放在Runnable 中,然后执行吗?如果是这样,我担心它会过多地分割代码的目的
  • 我不知道您的代码库是什么样的,但根据我的经验,将每个工作单元设为 Runnable 实例不会使代码库碎片化,反而通常会使事情更具可读性(并使每个操作的上下文很好地包含在单个对象中)。更重要的是,它更适合 Executor 模型,并且可以轻松构建其他机制,如取消、等待结果,甚至并行化,如果这适合您的任务。一般:Thread是低级抽象;尽可能使用更高级别的抽象。
【解决方案2】:

如果您想捕获发送到线程的中断,例如执行清理操作,您无法绕过将整个 run() 代码块放在 try/catch 异常块中。原因是如果您的线程正在执行任何类型的线程间信号,例如通过条件变量或信号量(调用wait() 方法),您的run() 代码块将立即抛出InterruptedException,如果为线程设置了中断状态标志。但是,如果您想主动检查线程的中断状态,最简单的方法是喷射一堆中断点,也就是您的 checkPause() 方法,然后休眠几纳秒(您的 @987654324如果设置了中断标志,@方法将抛出 InterruptedException)。这可以在不影响线程性能的情况下实现您的目标。

【讨论】:

  • 就效果而言,这听起来确实很有效,如果您只想要一种方法来方便地停止被中断的线程。
【解决方案3】:

请确保您要实现的目标确实有意义,并且不属于让我们重新发明轮子类别。制作任何通用的一刀切的停止方法都不是一件容易的事,尽管对于 Java 创建者来说也是如此。这就是不推荐使用 Thread.stop() 方法的原因。 (https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#stop())(而且 Thread.destroy() 并没有真正实现 AFAICT。)

有一个很好的解释:https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html

【讨论】:

  • OP 使用了“停止”这个词,但 t.stop() 所做的事情与 OP 要求的完全不同。 stop() 调用的接收端线程在这件事上没有发言权:其他线程可以随时调用t.stop(),如果它恰好在中间破坏了一些重要的数据结构。另一方面,OP 要求的是线程t 以一种干净的方式检查,在安全点,它是否被要求暂停或停止。
  • @besmirched 这就是为什么我提供了一个指向描述如何使用中断的教程的链接。 ;-) 虽然没有严格提到这个问题,但我经常看到人们试图在 Java 中实现“生活在他们脑海中的线程模型”,而不是使用 Java 中实际存在的线程模型。当然,出于学术目的,我也使用 Java 的监视器来实现信号量,但这并不是应该如何使用(“低级”)线程的。最后但同样重要的是,这也是未来人们从搜索引擎访问此页面的参考。
  • 您的假设可能是正确的。不过,失败的尝试通常很有教育意义
  • @Sudix 但是当然!恕我直言,没有比实验更好的学习编程方法了。麻烦的是很多人都在使用 SO 作为盲目复制粘贴源来创建他们的生产级应用程序......;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-06
相关资源
最近更新 更多