JavaFX的并发性

 要访问完整的JAVA中文教程请点击这里:存在码 cunzaima.cn

javafx.concurrent包概述

Java平台通过java.util.concurrent包提供了一套完整的并发库。javafx.concurrent包通过考虑JavaFX应用程序线程和GUI开发人员面临的其他限制,利用现有的API。

javafx.concurrent包由Worker接口和两个具体实现TaskService类组成。 Worker接口提供了对于后台工作线程与UI通信的有用的API。 Task类是java.util.concurrent.FutureTask类的完全可观察实现。 Task类使开发人员能够在JavaFX应用程序中实现异步任务。 Service类执行任务。

WorkerStateEvent类指定了当Worker实现的状态发生变化时发生的事件。 TaskService类都实现了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对象正在进行的工作进度可以通过三个不同的属性获得,例如totalWorkworkDoneprogress

有关参数值范围的更多信息,请参阅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方法来更新任务的progresstotalWorkworkDone属性。这很重要,因为现在您可以使用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, backoffStrategymaximumFailureCount属性的值重新启动或退出。

如果restartOnFailure属性为false,则ScheduledService对象会转换为FAILED状态并退出。在这种情况下,您可以手动重新启动失败的ScheduledService对象。

如果restartOnFailure属性为true,则ScheduledService对象会转换为SCHEDULED状态,并在cumulativePeriod属性的持续时间内保持在该状态,该属性是通过调用backoffStrategy属性获得的。使用cumulativePeriod属性,您可以强制失败的ScheduledService对象在下次运行之前等待更长的时间。在ScheduledService成功完成后,cumulativePeriod属性会重置为period属性的值。当连续失败的次数达到maximumFailureCount属性的值时,ScheduledService对象会转换为FAILED状态并退出。

ScheduledService对象运行时发生的delayperiod属性的任何更改都将在下一次迭代中生效。delayperiod属性的默认值都设置为0。

结论

在本章中,您学习了javafx.concurrent包提供的基本功能,并熟悉了Task和Service类实现的几个示例。有关如何正确创建Task实现的更多示例,请参阅Task类的API文档。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值