28

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

我怎么能做这样的事情?

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

4

15 回答 15

12

尝试一些人所说的 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;
}
于 2008-09-18T18:52:10.470 回答
8

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

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

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

这种方法本质上是不安全的。使用 Thread.stop 停止线程会导致它解锁所有已锁定的监视器(这是未经检查的 ThreadDeath 异常沿堆栈传播的自然结果)。如果以前受这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。停止的许多用法应该由简单地修改一些变量以指示目标线程应该停止运行的代码替换。目标线程应该定期检查这个变量,如果变量指示它要停止运行,则以有序的方式从它的 run 方法中返回。如果目标线程等待很长时间(例如,在条件变量上),应该使用中断方法来中断等待。有关详细信息,请参阅为什么不推荐使用 Thread.stop、Thread.suspend 和 Thread.resume?

于 2008-09-18T16:13:06.843 回答
4

与其滚动你自己的布尔标志,为什么不直接使用 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
    }
}
于 2008-09-18T16:31:05.347 回答
3

这不是有点像问“当除了 Thread.stop() 之外没有其他方法可用时,我如何中止线程?”

显然,唯一有效的答案是 Thread.stop()。它丑陋,在某些情况下可能会破坏东西,可能导致内存/资源泄漏,并且被 TLEJD(非凡的 Java 开发者联盟)不赞成,但是在像这样的一些情况下它仍然很有用。如果第三方代码没有可用的 close 方法,则真的没有任何其他方法。

OTOH,有时会有后门关闭方法。即,关闭它使用的底层流,或者它需要完成它的工作的一些其他资源。然而,这很少比调用 Thread.stop() 并让它遇到 ThreadDeathException 更好。

于 2008-09-18T16:58:22.443 回答
2

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

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();
  }
}

对于那些用户已经跳过渲染引擎并且你想扔掉到目前为止已经安排好的工作的情况。

于 2008-10-27T17:28:00.710 回答
1

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

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

我不建议使用Thread.stop它,因为我上次检查时已弃用它。

编辑:

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

于 2008-09-18T16:16:56.647 回答
1

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

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

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

static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();
于 2008-09-18T16:19:32.910 回答
1

不幸的是,杀死一个线程本质上是不安全的,因为有可能使用可以通过锁同步的资源,如果你杀死的线程当前有锁,可能会导致程序陷入死锁(不断尝试获取无法获得的资源) . 您必须手动检查它是否需要从您要停止的线程中终止。Volatile 将确保检查变量的真实值,而不是之前可能已存储的值。附带说明 Thread.join 在退出线程上,以确保您等到垂死的线程实际消失后再执行任何操作,而不是一直检查。

于 2008-09-18T16:23:13.777 回答
1

您似乎无法控制渲染屏幕的线程,但您似乎可以控制微调器组件。我会在线程渲染屏幕时禁用微调器。这样,用户至少有一些与他们的行为有关的反馈。

于 2008-09-18T18:31:25.293 回答
1

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

于 2008-09-18T18:52:46.660 回答
1

旨在使用布尔字段的解决方案是正确的方向。但该领域必须是易变的。Java 语言规范

“例如,在以下(损坏的)代码片段中,假设 this.done 是一个非易失性布尔字段:

而(!this.done)
  线程.sleep(1000);

编译器可以自由地读取 this.done 字段一次,并在每次执行循环时重用缓存的值。这意味着循环永远不会终止,即使另一个线程更改了 this.done 的值。”

据我记得“实践中的 Java 并发”的目的是使用 java.lang.Thread 的interrupt()interrupted()方法。

于 2008-09-19T22:01:14.850 回答
0

也许这可以帮助你:我们如何在 Java 中杀死一个正在运行的线程?

您可以通过设置外部类变量来终止特定线程。

 Class Outer
 {    
    public static flag=true;
    Outer()
    {
        new Test().start();
    } 
    class Test extends Thread
    {               
       public void run()
       {
         while(Outer.flag)
         {
          //do your work here
         }  
       }
    }
  } 

如果要停止上述线程,请将 flag 变量设置为false. 杀死线程的另一种方法是在 ThreadGroup 中注册它,然后调用destroy(). 这种方式也可用于通过将它们创建为组或向组注册来杀死类似的线程。

于 2008-09-18T16:16:54.460 回答
0

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

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

这种方法本质上是不安全的。使用 Thread.stop 停止线程会导致它解锁所有已锁定的监视器(这是未经检查的 ThreadDeath 异常沿堆栈传播的自然结果)。如果以前受这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。

可以在此处找到有关此主题的更多信息。

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

于 2008-09-18T16:42:39.593 回答
0

您是否考虑过让线程观察您通过界面更改的属性,而不是玩线程启动和停止?在某些时候,您仍然需要线程的停止条件,但这也可以做到。如果你是 MVC 的粉丝,这很适合这种设计

抱歉,在重新阅读您的问题后,这个或任何其他“检查变量”建议都不能解决您的问题。

于 2008-09-18T16:44:25.783 回答
0

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

您应该使用 Executors,请参阅包:java.util.concurrent

于 2008-10-30T17:16:21.870 回答