关于多线程:如何在Java中快速干净地中止线程?

关于多线程:如何在Java中快速干净地中止线程?

How to abort a thread in a fast and clean way in java?

这是我的问题:我有一个对话框,其中包含一些用户可以更改的参数(例如,通过微调器)。 每次更改这些参数之一时,我都会启动一个线程以根据新的参数值更新3D视图。
如果在第一个线程正在工作时用户更改了另一个值(或通过多次单击微调箭头再次更改了相同的值),我想中止第一个线程(以及3D视图的更新)并启动一个新线程 具有最新的参数值。

我该怎么做?

PS:我的线程的run()方法中没有循环,因此检查标志不是一个选择:更新3D视图的线程基本上只调用一个执行时间很长的方法。 我无法在此方法中添加任何要求中止的标志,因为我无权访问其代码。


如某些人所述,尝试使用interrupt()来查看它是否对您的线程有任何影响。如果不是,请尝试破坏或关闭将使线程停止的资源。这比尝试抛出Th??read.stop()更好。

如果性能是可以忍受的,则可以将每个3D更新视为离散的不间断事件,然后让其运行到结论,然后检查是否有新的最新更新要执行。这可能会使GUI变得有些混乱,因为用户将能够进行五处更改,然后查看五处更改之前的图形结果,然后查看其最新更改的结果。但是,取决于此过程的持续时间,它可能是可以容忍的,并且可以避免不得不杀死线程。设计可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
boolean stopFlag = false;
Object[] latestArgs = null;

public void run() {
  while (!stopFlag) {
    if (latestArgs != null) {
      Object[] args = latestArgs;
      latestArgs = null;
      perform3dUpdate(args);
    } else {
      Thread.sleep(500);
    }
  }
}

public void endThread() {
  stopFlag = true;
}

public void updateSettings(Object[] args) {
  latestArgs = args;
}

正在更新3D视图的线程应定期检查某些标志(使用volatile boolean)以查看是否应终止。要中止线程时,只需设置标志。当线程下一次检查该标志时,它应该简单地脱离它用于更新视图并从其run方法返回的任何循环。

如果您确实无法访问代码,则线程正在运行以使其检查标志,则没有安全的方法来停止线程。在您的应用程序完成之前,该线程是否会正常终止?如果是这样,是什么导致它停止?

如果它运行了很长一段时间,而您只需要结束它,则可以考虑使用不推荐使用的Thread.stop()方法。但是,有充分的理由不推荐使用。如果该线程在某些操作过程中停止,导致某些状态处于不一致状态或某些资源未正确清理,则可能会遇到麻烦。这是文档中的注释:

This method is inherently unsafe.
Stopping a thread with Thread.stop
causes it to unlock all of the
monitors that it has locked (as a
natural consequence of the unchecked
ThreadDeath exception propagating up
the stack). If any of the objects
previously protected by these monitors
were in an inconsistent state, the
damaged objects become visible to
other threads, potentially resulting
in arbitrary behavior. Many uses of
stop should be replaced by code that
simply modifies some variable to
indicate that the target thread should
stop running. The target thread should
check this variable regularly, and
return from its run method in an
orderly fashion if the variable
indicates that it is to stop running.
If the target thread waits for long
periods (on a condition variable, for
example), the interrupt method should
be used to interrupt the wait. For
more information, see Why are
Thread.stop, Thread.suspend and
Thread.resume Deprecated?


为什么不使用Java线程中已经存在的线程中断机制,而不是滚动自己的布尔标志?根据无法更改的内部代码实现方式,您也许也可以中止其部分执行。

外螺纹:

1
2
3
4
5
6
7
8
9
10
11
if(oldThread.isRunning())
{
    oldThread.interrupt();
    // Be careful if you're doing this in response to a user
    // action on the Event Thread
    // Blocking the Event Dispatch Thread in Java is BAD BAD BAD
    oldThread.join();
}

oldThread = new Thread(someRunnable);
oldThread.start();

内部可运行/线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void run()
{
    // If this is all you're doing, interrupts and boolean flags may not work
    callExternalMethod(args);
}

public void run()
{
    while(!Thread.currentThread().isInterrupted)
    {
        // If you have multiple steps in here, check interrupted peridically and
        // abort the while loop cleanly
    }
}

这是否有点像问"当没有除Thread.stop()之外的其他方法不可用时如何中止线程?"

显然,唯一有效的答案是Thread.stop()。它的丑陋,在某些情况下可能会破坏事情,可能导致内存/资源泄漏,并且被TLEJD(非凡Java开发者联盟)所抵制,但是在某些情况下它仍然很有用。如果第三方代码没有可用的关闭方法,那么实际上没有任何其他方法。

OTOH,有时有后门关闭方法。即,关闭与其一起使用的基础流或完成其工作所需的其他资源。但是,这仅比调用Thread.stop()并使其经历ThreadDeathException更好。


该问题的公认答案允许您将批处理工作提交到后台线程中。 这可能是一个更好的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class dispatcher< T > extends Thread {

  protected abstract void processItem(T work);

  private List< T > workItems = new ArrayList< T >();
  private boolean stopping = false;
  public void submit(T work) {
    synchronized(workItems) {
      workItems.add(work);
      workItems.notify();
    }
  }
  public void exit() {
    stopping = true;
    synchronized(workItems) {
      workItems.notifyAll();
    }
    this.join();
  }
  public void run() {
    while(!stopping) {
      T work;
      synchronized(workItems) {
        if (workItems.empty()) {
            workItems.wait();
            continue;
        }
        work = workItems.remove(0);
      }
      this.processItem(work);
    }
  }
}

要使用该类,请对其进行扩展,并为T提供一个类型和processItem()的实现。 然后只需构造一个并在其上调用start()即可。

您可以考虑添加abortPending方法:

1
2
3
4
5
public void abortPending() {
  synchronized(workItems) {
    workItems.clear();
  }
}

对于用户跳过渲染引擎的情况,而您想丢弃到目前为止已安排的工作的情况。


旨在使用布尔字段的解决方案是正确的方向。但是这个领域必须是动荡的。
Java语言规范说:

"For example, in the following (broken) code fragment, assume that this.done is a non-
volatile boolean field:

1
2
while (!this.done)
  Thread.sleep(1000);

The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done."

据我所知," Pratice中的Java并发性"旨在使用java.lang.Thread的interrupt()和interrupted()方法。


我建议您通过使用wait和notify来阻止多个线程,这样,如果用户多次更改该值,它将只运行一次该线程。如果用户将值更改10次,它将在第一次更改时触发线程,然后在线程完成之前进行的所有更改都会"汇总"到一个通知中。那不会停止一个线程,但是根据您的描述,没有很好的方法可以做到这一点。


您似乎对呈现屏幕的线程没有任何控制权,但您似乎确实对微调器组件具有控制权。当线程渲染屏幕时,我将禁用微调器。这样,用户至少具有一些与其行为有关的反馈。


不幸的是,由于有可能使用可以通过锁同步的资源,因此杀死线程本质上是不安全的,如果您杀死的线程当前具有锁,则可能导致程序陷入死锁状态(不断尝试获取无法获取的资源) 。您将必须手动检查是否需要从要停止的线程中将其杀死。易失性将确保检查变量的真实值,而不是以前可能已存储的内容。附带说明一下,退出线程上的Thread.join可以确保您等到即将死掉的线程真正消失之后再执行任何操作,而不是一直检查。


一旦线程的run()方法完成,它将退出,因此您需要进行一些检查以使其完成该方法。

您可以中断线程,然后进行一些检查,该检查将定期检查isInterrupted()并返回run()方法。

您还可以使用一个布尔值,该布尔值会在线程内进行定期检查,如果是,则使其返回;或者,如果正在执行重复性任务,则将该线程放入循环中,然后在设置布尔值时退出run()方法。例如,

1
2
3
4
5
6
7
8
static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();

我过去实现这种方法的方式是在我的Runnable子类中实现一个shutdown()方法,该方法将名为should_shutdown的实例变量设置为true。 run()方法通常在循环中执行某些操作,并将定期检查should_shutdown,当它为true时,返回,或调用do_shutdown()然后返回。

您应该方便地引用当前工作线程,并且当用户更改值时,请在当前线程上调用shutdown(),然后等待其关闭。然后,您可以启动一个新线程。

我不建议使用Thread.stop,因为上次我不推荐使用它。

编辑:

阅读有关您的工作线程如何仅调用另一个需要花费一些时间才能运行的方法的评论,因此以上内容不适用。在这种情况下,您唯一真正的选择是尝试调用interrupt()并查看是否有效果。如果不是,请考虑以某种方式手动导致工作线程正在调用的函数中断。例如,听起来好像正在做一些复杂的渲染,所以可能破坏画布并引发异常。这不是一个很好的解决方案,但是据我所知,这是在这样的调试中停止线程的唯一方法。


正确的答案是不使用线程。

您应该使用Executors,请参见软件包:java.util.concurrent


您是否考虑过让线程观察通过接口更改的属性,而不是开始和停止线程?您仍然会在某个时候希望线程处于停止状态,但这也可以做到。如果您是MVC的粉丝,那么它非常适合这种设计

抱歉,重新阅读您的问题后,该建议或其他"检查变量"建议均无法解决您的问题。


由于您正在处理代码,因此可能无法访问您。标准过程(如其他答案所述)是要有一个正在运行的线程定期检查的标志。如果设置了该标志,请执行清理并退出。

由于该选项对您不可用,因此唯一的其他选择是强制退出正在运行的进程。过去可以通过调用Thread.stop()来实现,但是由于以下原因(从javadocs复制),该方法已被永久弃用:

This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior.

有关此主题的更多信息,请参见此处。

一种可以确保完成请求的绝对确定的方法(尽管这不是一种非常有效的方法)是通过Runtime.exec()启动一个新的Java进程,然后根据需要通过Process.destroy()停止该进程。但是,在这样的进程之间共享状态并不是一件容易的事。


也许这可以为您提供帮助:如何杀死Java中正在运行的线程?

You can kill a particular thread by setting an external class variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 Class Outer
 {    
    public static flag=true;
    Outer()
    {
        new Test().start();
    }
    class Test extends Thread
    {              
       public void run()
       {
         while(Outer.flag)
         {
          //do your work here
         }  
       }
    }
  }

if you want to stop the above thread, set flag variable to false. The other way to kill a thread is just registering it in ThreadGroup, then call destroy(). This way can also be used to kill similar threads by creating them as group or register with group.


推荐阅读