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。