2014-10-08 163 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); 
     } 

    } 
}