2014-10-08 166 views
0

我知道Java的实际模型是用于协作线程的,它强制线程死亡不会发生。在PropertyListener中杀死一个线程(JavaFX8)

由于Thread.stop()已弃用(基于上述原因)。我试图通过一个BooleanProperty监听器来停止线程。

这里是MCVE:

TestStopMethod.java

package javatest; 
import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.value.ObservableValue; 
public class TestStopMethod extends Thread { 
    private BooleanProperty amIdead = new SimpleBooleanProperty(false); 
    public void setDeath() { 
     this.amIdead.set(true); 
    } 

    @Override 
    public void run() { 
     amIdead.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { 
      System.out.println("I'm dead!!!"); 
      throw new ThreadDeath(); 
     }); 
     for(;;); 
    } 
} 

WatchDog.java

package javatest; 

import java.util.TimerTask; 

public class Watchdog extends TimerTask { 
    TestStopMethod watched; 
    public Watchdog(TestStopMethod target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.setDeath(); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 

} 

Driver.java

package javatest; 

import java.util.*; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

public class Driver { 

    public static void main(String[] args) { 
     try { 
      TestStopMethod mythread = new TestStopMethod(); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(mythread); 
      t.schedule(w, 1000); 
      mythread.start(); 
      mythread.join(); 
      t.cancel(); 
      System.out.println("End of story"); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
} 
+1

你认为哪一个线程被抛出'ThreadDeath'错误? – 2014-10-08 18:05:25

+0

我想,如果我添加一个监听器到一个属性并在WatchDog中改变它的值,它会引发这个异常。现在的代码保持运行,所以'mythread.join()'永远不会被调用。 – DeMarco 2014-10-08 18:09:56

+1

它将从改变属性的线程抛出,这是支持定时器实例的线程。 (如果你仔细想想,基本上不可能安排在任意线程上调用监听器。)我认为你将拥有该方法的对象与执行该方法的线程混淆了。 – 2014-10-08 18:12:01

回答

1

如果更改属性值,则会在属性发生更改的相同线程上调用侦听器(请考虑如何实现属性类)。因此,在您的示例中,ThreadDeath错误是从支持Timer实例的线程抛出的,这不是您真正想要的。

从外部(到该线程)终止线程的正确方法是设置一个标志,然后在线程的实现中定期轮询该标志。这实际上比听起来更棘手,因为标志必须从多个线程访问,所以访问它必须正确同步。

幸运的是,有一些工具类可以帮助实现这一点。例如,FutureTask包装RunnableCallable并提供cancel()isCancelled()方法。如果您使用的是JavaFX,那么javafx.concurrent API提供了一些实现CallableRunnable的实现,并且还提供了专门用于在FX应用程序线程上执行代码的功能。有些例子可以看看documentation for javafx.concurrent.Task

因此,举例来说,你可以这样做:

package javatest; 
public class TestStopMethod implements Runnable { 

    @Override 
    public void run() { 
     try { 
      synchronized(this) { 
       for(;;) { 
        wait(1); 
       } 
      } 
     } catch (InterruptedException exc) { 
      System.out.println("Interrupted"); 
     } 
    } 
} 

Watchdog.java:

package javatest; 

import java.util.TimerTask; 
import java.util.concurrent.Future; 

public class Watchdog extends TimerTask { 
    Future<Void> watched; 
    public Watchdog(Future<Void> target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.cancel(true); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 
} 

Driver.java:

package javatest; 

import java.util.*; 
import java.util.concurrent.FutureTask; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

public class Driver { 

    public static void main(String[] args) { 
     try { 
      FutureTask<Void> myTask = new FutureTask<>(new TestStopMethod(), null); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(myTask); 
      t.schedule(w, 1000); 
      Thread mythread = new Thread(myTask); 
      mythread.start(); 
      mythread.join(); 
      t.cancel(); 
      System.out.println("End of story"); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
} 

在JavaFX应用程序,你可能会做它是这样的。请注意,如果尝试在没有运行FX应用程序线程的情况下执行此操作,情况会变糟,因为必须在该线程上更新FX Task中的cancelled标志。

package javatest; 

import javafx.concurrent.Task; 

public class TestStopMethod extends Task<Void> { 

    @Override 
    public Void call() { 
     System.out.println("Calling"); 
     while (true) { 
      if (isCancelled()) { 
       System.out.println("Cancelled"); 
       break ; 
      } 
     } 
     System.out.println("Exiting"); 
     return null ; 
    } 
} 

Watchdog.java:

package javatest; 

import java.util.TimerTask; 

import javafx.concurrent.Task; 

public class Watchdog extends TimerTask { 
    Task<Void> watched; 
    public Watchdog(Task<Void> target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.cancel(); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 

} 

Driver.java

package javatest; 

import java.util.Timer; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.concurrent.Worker; 
import javafx.scene.Scene; 
import javafx.scene.control.TextArea; 
import javafx.scene.layout.BorderPane; 
import javafx.stage.Stage; 

public class Driver extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     try { 

      TextArea console = new TextArea(); 
      BorderPane root = new BorderPane(console); 
      Scene scene = new Scene(root, 600, 400); 
      primaryStage.setScene(scene); 
      primaryStage.show(); 

      Task<Void> myTask = new TestStopMethod(); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(myTask); 
      t.schedule(w, 1000); 
      Thread mythread = new Thread(myTask); 
      mythread.setDaemon(true); 

      myTask.stateProperty().addListener((obs, oldState, newState) -> { 
       console.appendText("State change "+oldState+" -> "+newState+"\n"); 
       if (oldState == Worker.State.RUNNING) { 
        t.cancel(); 
        console.appendText("End of Story\n"); 
       } 
      }); 
      mythread.start(); 

     } catch (Exception ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
}