javafx.concurrent包概述
Java平台通过java.util.concurrent包提供了一套完整的并发库。javafx.concurrent包通过考虑JavaFX应用程序线程和GUI开发人员面临的其他限制,利用现有的API。
javafx.concurrent包由Worker接口和两个具体实现Task和Service类组成。 Worker接口提供了对于后台工作线程与UI通信的有用的API。 Task类是java.util.concurrent.FutureTask类的完全可观察实现。 Task类使开发人员能够在JavaFX应用程序中实现异步任务。 Service类执行任务。
WorkerStateEvent类指定了当Worker实现的状态发生变化时发生的事件。 Task和Service类都实现了EventTarget接口,因此支持监听状态事件。
Worker接口
Worker接口定义了在一个或多个后台线程上执行某些工作的对象。 Worker对象的状态是可观察的,并且可以从JavaFX应用程序线程中使用。
Worker对象的生命周期定义如下。创建时,Worker对象处于READY状态。当被安排工作时,Worker对象转换为SCHEDULED状态。之后,当Worker对象执行工作时,其状态变为RUNNING。请注意,即使Worker对象在没有被安排的情况下立即启动,它也会首先转换为SCHEDULED状态,然后转换为RUNNING状态。成功完成的Worker对象的状态为SUCCEEDED,并且value属性设置为此Worker对象的结果。否则,如果在执行Worker对象期间抛出任何异常,其状态将变为FAILED,并且exception属性设置为发生的异常类型。在Worker对象结束之前,开发人员可以随时通过调用cancel方法来中断它,这将使Worker对象进入CANCELLED状态。
有关Worker对象正在进行的工作进度可以通过三个不同的属性获得,例如totalWork,workDone和progress。
有关参数值范围的更多信息,请参阅API文档。
Task类
任务(Task)用于在后台线程上实现需要完成的工作的逻辑。首先,您需要扩展Task类。您的Task类的实现必须重写call方法来执行后台工作并返回结果。
call方法在后台线程上调用,因此该方法只能操作从后台线程安全读取和写入的状态。例如,从call方法中操作活动场景图会引发运行时异常。另一方面,Task类设计用于与JavaFX GUI应用程序一起使用,并确保对公共属性的任何更改、错误或取消的更改通知、事件处理程序和状态发生在JavaFX应用程序线程上。在call方法内部,您可以使用updateProgress、updateMessage、updateTitle方法,这些方法会在JavaFX应用程序线程上更新相应属性的值。但是,如果任务被取消,call方法的返回值将被忽略。
请注意,Task类适用于Java并发库,因为它继承自java.utils.concurrent.FutureTask类,该类实现了Runnable接口。因此,Task对象可以在Java并发Executor API中使用,并且还可以作为参数传递给线程。您可以使用FutureTask.run()方法直接调用Task对象,从而可以从另一个后台线程调用此任务。了解Java并发API将有助于您理解JavaFX中的并发。
任务可以通过以下方式之一启动:
-
通过将给定任务作为参数启动线程:
Thread th = new Thread(task);th.setDaemon(true);th.start(); -
使用ExecutorService API:
ExecutorService.submit(task);
Task类定义了一个一次性对象,不能重复使用。如果需要可重用的Worker对象,请使用Service类。
取消任务
在Java中没有可靠的方法来停止正在进行的线程。然而,当任务上调用cancel时,任务必须停止处理。任务应该在call方法的主体中使用isCancelled方法定期检查是否被取消。示例1-1展示了一个正确实现了Task类并检查取消的示例。
示例1-1
import javafx.concurrent.Task;
Task<Integer> task = new Task<Integer>() {
@Override protected Integer call() throws Exception {
int iterations;
for (iterations = 0; iterations < 100000; iterations++) {
if (isCancelled()) {
break;
}
System.out.println("迭代 " + iterations);
}
return iterations;
}
};
如果任务实现中有阻塞调用,例如Thread.sleep,并且任务在阻塞调用期间被取消,将抛出InterruptedException。对于这些任务,中断的线程可能是取消任务的信号。因此,具有阻塞调用的任务必须再次检查isCancelled方法,以确保InterruptedException是由任务取消引起的,如示例1-2所示。
示例1-2
import javafx.concurrent.Task;
Task<Integer> task = new Task<Integer>() {
@Override protected Integer call() throws Exception {
int iterations;
for (iterations = 0; iterations < 1000; iterations++) {
if (isCancelled()) {
updateMessage("已取消");
break;
}
updateMessage("迭代 " + iterations);
updateProgress(iterations, 1000);
// 阻塞线程一段时间,但一定要检查InterruptedException以进行取消
try {
Thread.sleep(100);
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("已取消");
break;
}
}
}
return iterations;
}
};
显示后台任务的进度
在多线程应用程序中,显示后台任务的进度是一个典型的用例。假设您有一个从一数到一百万的后台任务和一个进度条,您必须在后台计数器运行时更新此进度条上的进度。 示例 1-3 展示了如何更新进度条。
示例 1-3
import javafx.concurrent.Task;
Task task = new Task<Void>() {
@Override public Void call() {
static final int max = 1000000;
for (int i=1; i<=max; i++) {
if (isCancelled()) {
break;
}
updateProgress(i, max);
}
return null;
}
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
首先,通过重写call方法创建任务,在该方法中实现要执行的工作逻辑,并调用updateProgress方法来更新任务的progress、totalWork和workDone属性。这很重要,因为现在您可以使用progressProperty方法来获取任务的进度,并将进度条的进度绑定到任务的进度。
Service类
Service类被设计用于在一个或多个后台线程上执行Task对象。Service类的方法和状态只能在JavaFX应用程序线程上访问。这个类的目的是帮助开发人员实现后台线程和JavaFX应用程序线程之间的正确交互。
您可以对Service对象进行以下控制:根据需要启动、取消和重新启动。要启动Service对象,请使用Service.start()方法。
使用Service类,您可以观察后台工作的状态,并可选择取消它。稍后,您可以重置服务并重新启动它。因此,服务可以以声明方式定义,并根据需要重新启动。
对于需要自动重新启动的服务,请参见ScheduledService类部分。
在实现Service类的子类时,请确保将输入参数公开为子类的属性。
服务可以以以下方式之一执行:
-
通过Executor对象(如果为给定的服务指定了Executor)
-
通过守护线程(如果未指定执行程序)
-
通过自定义执行程序,如ThreadPoolExecutor
示例1-4展示了一个实现Service类的示例,该示例从任何URL中读取第一行并将其作为字符串返回。
示例1-4
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class FirstLineServiceApp extends Application {
@Override
public void start(Stage stage) throws Exception {
FirstLineService service = new FirstLineService();
service.setUrl("http://google.com");
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent t) {
System.out.println("done:" + t.getSource().getValue());
}
});
service.start();
}
public static void main(String[] args) {
launch();
}
private static class FirstLineService extends Service<String> {
private StringProperty url = new SimpleStringProperty();
public final void setUrl(String value) {
url.set(value);
}
public final String getUrl() {
return url.get();
}
public final StringProperty urlProperty() {
return url;
}
@Override
protected Task<String> createTask() {
return new Task<String>() {
@Override
protected String call()
throws IOException, MalformedURLException {
try ( BufferedReader in = new BufferedReader(
new InputStreamReader(
new URL(getUrl()).openStream;
in = new BufferedReader(
new InputStreamReader(u.openStream()))) {
return in.readLine();
}
}
};
}
}
WorkerStateEvent类和状态转换
每当Worker实现的状态发生变化时,会发生相应的事件,这些事件由WorkerStateEvent类定义。例如,当Task对象转换到SUCCEEDED状态时,会发生WORKER_STATE_SUCCEEDED事件,然后调用onSucceeded事件处理程序,之后在JavaFX应用程序线程上调用受保护的便利方法succeeded。
当Worker实现转换到相应的状态时,还有一些受保护的便利方法,例如cancelled、failed、running、scheduled和succeeded,这些方法可以在Task和Service类的子类中被重写,以实现应用程序的逻辑。示例1-5展示了一个Task实现,它在任务成功、取消和失败时更新状态消息。
示例1-5
import javafx.concurrent.Task;
Task<Integer> task = new Task<Integer>() {
@Override protected Integer call() throws Exception {
int iterations = 0;
for (iterations = 0; iterations < 100000; iterations++) {
if (isCancelled()) {
break;
}
System.out.println("Iteration " + iterations);
}
return iterations;
}
@Override protected void succeeded() {
super.succeeded();
updateMessage("完成!");
}
@Override protected void cancelled() {
super.cancelled();
updateMessage("已取消!");
}
@Override protected void failed() {
super.failed();
updateMessage("失败!");
}
};
ScheduledService类
许多涉及轮询的用例需要一个能够自动重新启动的服务。为了满足这些需求,将Service类扩展为ScheduledService类。ScheduledService类表示一个在成功执行后自动重新启动,并在特定条件下在失败后重新启动的服务。
创建ScheduledService对象时,它处于READY状态。
调用ScheduledService.start()或ScheduledService.restart()方法后,ScheduledService对象会在delay属性指定的持续时间内转换为SCHEDULED状态。
在RUNNING状态下,ScheduledService对象执行其任务。
任务成功完成
任务完成后,ScheduledService对象会先转换为SUCCEEDED状态,然后转换为READY状态,最后再转换为SCHEDULED状态。处于SCHEDULED状态的持续时间取决于上次转换为RUNNING状态的时间、当前时间以及period属性的值,该属性定义了两次连续运行之间的最小时间间隔。如果上次执行在期限到期之前完成,则ScheduledService对象会一直保持在SCHEDULED状态,直到期限到期。否则,如果上次执行时间超过了指定的期限,则ScheduledService对象会立即转换为RUNNING状态。
任务失败
如果任务以FAILED状态终止,ScheduledService对象会根据restartOnFailure, backoffStrategy和maximumFailureCount属性的值重新启动或退出。
如果restartOnFailure属性为false,则ScheduledService对象会转换为FAILED状态并退出。在这种情况下,您可以手动重新启动失败的ScheduledService对象。
如果restartOnFailure属性为true,则ScheduledService对象会转换为SCHEDULED状态,并在cumulativePeriod属性的持续时间内保持在该状态,该属性是通过调用backoffStrategy属性获得的。使用cumulativePeriod属性,您可以强制失败的ScheduledService对象在下次运行之前等待更长的时间。在ScheduledService成功完成后,cumulativePeriod属性会重置为period属性的值。当连续失败的次数达到maximumFailureCount属性的值时,ScheduledService对象会转换为FAILED状态并退出。
在ScheduledService对象运行时发生的delay和period属性的任何更改都将在下一次迭代中生效。delay和period属性的默认值都设置为0。

