如何在Java中以快速干净的方式中止线程?
这是我的问题:我有一个对话框,其中包含一些用户可以更改的参数(例如,通过微调器)。每次更改这些参数之一时,我都会启动一个线程,以根据新的参数值更新3D视图。
如果用户在第一个线程正在工作时更改了另一个值(或者通过多次单击微调箭头再次更改了相同的值),我想中止第一个线程(以及3D视图的更新)并启动一个新具有最新的参数值。
我该怎么做?
PS:我的线程的run()方法中没有循环,因此检查标志不是一个选择:更新3D视图的线程基本上只调用一个执行时间很长的方法。我无法在此方法中添加任何要求中止的标志,因为我无权访问其代码。
解决方案
更新3D视图的线程应定期检查一些标志(使用" volatile布尔值")以查看是否应终止。要中止线程时,只需设置标志。当线程接下来检查该标志时,它应该简单地跳出它用于更新视图并从其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中正在运行的线程?
You can kill a particular thread by setting an external class variable.
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.
我过去实现这种方法的方法是在我的Runnable子类中实现一个Shutdown()方法,该方法将名为should_shutdown的实例变量设置为true。 run()
方法通常在循环中执行某些操作,并将定期检查should_shutdown
,如果为true,则返回,或者调用do_shutdown()
然后返回。
我们应该方便地引用当前工作线程,并且当用户更改值时,请在当前线程上调用shutdown()
,然后等待其关闭。然后,我们可以启动一个新线程。
我不建议使用Thread.stop,因为上次我不推荐使用它。
编辑:
阅读有关工作线程如何仅调用另一个需要花费一些时间才能运行的方法的评论,因此以上内容不适用。在这种情况下,我们唯一真正的选择是尝试调用interrupt()
看看是否有效果。如果不是,请考虑以某种方式手动导致工作线程正在调用的函数中断。例如,听起来好像正在做一些复杂的渲染,所以可能破坏画布并引发异常。这不是一个很好的解决方案,但是据我所知,这是在这样的调试中停止线程的唯一方法。
一旦线程的run()方法完成,它将退出,因此我们需要进行一些检查以使其完成该方法。
我们可以中断线程,然后进行一些检查,该检查将定期检查isInterrupted()
并返回run()
方法之外。
我们还可以使用一个布尔值,该布尔值会在线程内进行定期检查,如果是,则使其返回;如果正在执行重复性任务,则将该线程放入循环中,然后在设置布尔值时退出run()方法。例如,
static boolean shouldExit = false; Thread t = new Thread(new Runnable() { public void run() { while (!shouldExit) { // do stuff } } }).start();
不幸的是,由于有可能使用可以通过锁同步的资源,因此杀死线程本质上是不安全的,如果我们杀死的线程当前具有锁,则可能导致程序陷入死锁(不断尝试获取无法获取的资源) 。我们将必须手动检查是否需要从要停止的线程中将其杀死。易失性将确保检查变量的真实值,而不是以前可能已存储的内容。附带说明一下,退出线程上的Thread.join可以确保我们等到即将死掉的线程真正消失之后再执行任何操作,而不是一直检查。
为什么不使用Java线程中已经存在的线程中断机制,而不是滚动自己的布尔标志?根据我们无法更改的代码内部实现方式,我们也许也可以中止其部分执行。
外螺纹:
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();
内部可运行/线程:
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()来实现,但是由于以下原因(从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()停止该进程。但是,在这样的进程之间共享状态并不是一件容易的事。
我们是否考虑过让线程观察通过接口更改的属性,而不是开始和停止线程?我们仍会在某个时刻希望线程处于停止状态,但这也可以做到。如果我们是MVC的粉丝,那么它非常适合这种设计
抱歉,重新阅读问题后,此建议或者任何其他"检查变量"建议均无法解决问题。
这是否有点像问"当没有Thread.stop()以外的方法不可用时,如何中止线程?"
显然,唯一有效的答案是Thread.stop()。它的丑陋,在某些情况下可能会破坏事情,可能导致内存/资源泄漏,并被TLEJD(非凡Java开发者联盟)所抵制,但是在某些情况下它仍然有用。如果第三方代码没有可用的关闭方法,那么实际上没有任何其他方法。
OTOH,有时有后门关闭方法。即,关闭与其一起使用的基础流或者完成其工作所需的其他资源。但是,这仅比调用Thread.stop()并使其经历ThreadDeathException更好。
我们似乎对呈现屏幕的线程没有任何控制权,但是我们似乎确实对微调器组件具有控制权。当线程渲染屏幕时,我将禁用微调器。这样,用户至少具有一些与其行为有关的反馈。
如某些人所述,尝试使用interrupt()来查看它是否对线程有任何影响。如果不是,请尝试破坏或者关闭将使线程停止的资源。这比尝试抛出Thread.stop()更好。
如果性能是可以忍受的,则可以将每个3D更新视为离散的不间断事件,然后让其运行到结论,然后检查是否有新的最新更新要执行。这可能会使GUI变得有些混乱,因为用户将能够进行五处更改,然后查看五处更改之前的图形结果,然后查看其最新更改的结果。但是,根据此过程的时间长短,它可能是可以容忍的,并且可以避免不得不杀死线程。设计可能如下所示:
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; }
我建议我们通过使用wait和notify来阻止多个线程,这样,如果用户多次更改该值,它将只运行一次该线程。如果用户将值更改10次,它将在第一次更改时触发线程,然后在线程完成之前进行的所有更改都会"汇总"到一个通知中。那不会停止线程,但是根据描述,没有很好的方法可以做到这一点。
旨在使用布尔字段的解决方案是正确的方向。但是这个领域必须是动荡的。
Java语言规范说:
"For example, in the following (broken) code fragment, assume that this.done is a non- volatile boolean field: 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()方法。
该问题的公认答案允许我们将批处理工作提交到后台线程中。这可能是一个更好的模式:
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方法:
public void abortPending() { synchronized(workItems) { workItems.clear(); } }
对于那些用户跳过渲染引擎并且我们想丢弃到目前为止已安排的工作的情况。
正确的答案是不使用线程。
我们应该使用Executors,请参见以下软件包:java.util.concurrent