JavaFX 多线程之 Task、Service、ScheduledService

2023-11-01

一、开发环境

IntelliJ IDEA 2020.2.3 + JDK 1.8 + JavaFX Scene Builder 2.0

二、javafx.concurrent 包

        JavaFX 场景图表示 JavaFX 应用程序的图形用户界面,它不是线程安全的,只能从 UI 线程(也称为 JavaFX 应用程序线程)访问和修改。在 JavaFX 应用程序线程上实现长时间运行的任务不可避免地会使应用程序 UI 无响应。最佳实践是在一个或多个后台线程上执行这些任务,并让 JavaFX 应用程序线程处理用户事件。

        如果有特殊要求或需要额外的代码处理能力,通过创建一个 Runnable 对象和一个新线程来实现后台 worker 是一种合适的方法。请注意,在某些时候必须与 JavaFX 应用程序线程通信,无论是结果还是后台任务的进度。

        对于大多数情况和大多数开发人员,推荐的方法是使用 javafx.concurrent 包提供的 JavaFX API,它负责与 UI 交互的多线程代码,并确保这种交互发生在正确的线程上。

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

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

        WorkerStateEvent 类指定了每当 Worker 实现的状态发生变化时发生的事件。 Task 和 Service 类都实现了 EventTarget 接口,因此支持监听状态事件。

在这里插入图片描述

1、Worker 接口

        Worker API 文档:Worker (JavaFX 2.2)

        Worker 接口定义了一个对象,该对象在一个或多个后台线程上执行某些工作。 Worker 对象的状态可以从 JavaFX 应用程序线程中观察和使用。

        Worker 对象的生命周期定义如下。创建时,Worker 对象处于 READY 状态。在计划进行工作后,Worker 对象将转换为 SCHEDULED 状态。之后,当 Worker 对象执行工作时,其状态变为 RUNNING。请注意,即使 Worker 对象在没有被调度的情况下立即启动,它也会首先转换为 SCHEDULED 状态,然后转换为 RUNNING 状态。成功完成的 Worker 对象的状态是 SUCCEEDED,value 属性设置为这个 Worker 对象的结果。否则,如果在 Worker 对象的执行过程中抛出任何异常,其状态将变为 FAILED,并且异常属性被设置为发生的异常的类型。在 Worker 对象结束之前的任何时间,开发人员都可以通过调用取消方法来中断它,该方法将 Worker 对象置于 CANCELED 状态。他们之间的关系如下图。

在这里插入图片描述

        ScheduledService 对象生命周期的区别可以在 ScheduledService 类部分找到。

        可以通过三个不同的属性(例如 totalWork,workDone 和 progress)获得由Worker对象完成的工作进度。

2、Task 类

        Task API 文档:Task (JavaFX 2.2)

        任务用于实现需要在后台线程上完成的工作逻辑。首先,需要扩展 Task 类。对 Task 类的实现必须覆盖 call 方法才能完成后台工作并返回结果。

        call 方法是在后台线程上调用的,因此该方法只能操作可以安全地从后台线程读取和写入的状态。例如,从 call 方法操作活动场景图会引发运行时异常。另一方面,Task 类旨在与 JavaFX GUI 应用程序一起使用,它确保对公共属性、错误或取消的更改通知、事件处理程序和状态的任何更改都发生在 JavaFX 应用程序线程上。在 call 方法中,您可以使用 updateProgress、updateMessage、updateTitle 方法,这些方法更新 JavaFX 应用程序线程上相应属性的值。但是,如果任务被取消,则调用方法的返回值将被忽略。

        请注意,Task 类适合 Java 并发库,因为它继承自实现 Runnable 接口的 java.utils.concurrent.FutureTask 类。因此,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 类。

① 取消 Task

        Java 中没有可靠的方法来停止进程中的线程。 但是,只要对任务调用取消,任务就必须停止处理。 任务应该在其工作期间定期检查它是否被调用方法主体中的 isCancelled 方法取消。 下面的示例显示了检查取消的 Task 类的正确实现。

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("Iteration " + iterations);
        }
        return iterations;
    }
};

        如果任务实现有阻塞调用,例如 Thread.sleep 并且任务在阻塞调用中被取消,则抛出 InterruptedException。 对于这些任务,中断的线程可能是取消任务的信号。 因此,具有阻塞调用的任务必须仔细检查 isCancelled 方法,以确保由于任务取消而引发了 InterruptedException,如下例所示。

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("Cancelled");
                break;
            }
            updateMessage("Iteration " + iterations);
            updateProgress(iterations, 1000);
 
            //Block the thread for a short time, but be sure
            //to check the InterruptedException for cancellation
            try {
                Thread.sleep(100);
            } catch (InterruptedException interrupted) {
                if (isCancelled()) {
                    updateMessage("Cancelled");
                    break;
                }
            }
        }
        return iterations;
    }
};

② 显示后台 Task 的进度

        多线程应用程序中的一个典型用例是显示后台任务的进度。 假设有一个从一到一百万计数的后台任务和一个进度条,当计数器在后台运行时,必须更新此进度条上的进度。 下面示例显示了如何更新进度条。

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 方法来检索任务的进度并将进度条绑定到任务的进度。

3、Service 类

        Service API 文档:Service (JavaFX 2.2)

        Service 类旨在在一个或多个后台线程上执行 Task 对象。服务类方法和状态只能在 JavaFX 应用程序线程上访问。这个类的目的是帮助开发者实现后台线程和 JavaFX Application 线程之间的正确交互。
        可以对Service对象进行以下控制:可以根据需要启动,取消和重新启动它。要启动 Service 对象,请使用 Service.start() 方法。
        使用 Service 类,可以观察后台工作的状态并可选择取消它。稍后,可以重置服务并重新启动它。因此,可以声明性地定义服务并按需重新启动。
        对于需要自动重启的服务,请参见 ScheduledService 类部分。
        在实现 Service 类的子类时,务必将输入参数作为子类的属性公开给 Task 对象。
        该服务可以通过以下方式之一执行:

  • 通过 Executor 对象,如果它是为给定的服务指定的
  • 通过守护线程,如果没有指定执行者
  • 通过自定义执行程序,例如 ThreadPoolExecutor

        下面示例显示了 Service 类的实现,它从任何 URL 读取第一行并将其作为字符串返回。

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("https://www.baidu.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();
                    }
                }
        };
    }
}

4、WorkerStateEvent 类和状态转换

        WorkerStateEvent API 文档:WorkerStateEvent (JavaFX 2.2)

        每当 Worker 实现的状态发生变化时,就会发生一个由 WorkerStateEvent 类定义的适当事件。 例如,当 Task 对象转换到 SUCCEEDED 状态时,发生 WORKER_STATE_SUCCEEDED 事件,调用 onSucceeded 事件处理程序,之后在 JavaFX 应用程序线程上调用成功的受保护的便捷方法。

        有 cancelled、failed、running、scheduled、successed 等几种受保护的便捷方法,它们在 Worker 实现转换到相应状态时被调用。 当状态更改以实现应用程序的逻辑时,这些方法可以被 Task 和 Service 类的子类覆盖。 下面示例显示了一个 Task 实现,它更新有关任务成功、取消和失败的状态消息。

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("Done!");
    }

    @Override protected void cancelled() {
        super.cancelled();
        updateMessage("Cancelled!");
    }

	@Override protected void failed() {
	    super.failed();
	    updateMessage("Failed!");
    }
};

5、ScheduledService 类

        许多涉及轮询的用例需要自动重启的服务。 为了满足这些需求,对 Service 类进行了扩展以生成 ScheduledService 类。 ScheduledService 类表示在成功执行后自动重启的服务,在特殊情况下,在失败时自动重启。

        在创建时,ScheduledService 对象处于 READY 状态。

        在调用 ScheduledService.start() 或 ScheduledService.restart() 方法后,ScheduledService 对象在延迟属性指定的持续时间内转换到 SCHEDULED 状态。

        在 RUNNING 状态下,ScheduledService 对象执行其任务。

① Task 成功完成

        任务完成后,ScheduledService 对象转换到 SUCCEEDED 状态,然后转换到 READY 状态,然后返回到 SCHEDULED 状态。 处于 SCHEDULED 状态的持续时间取决于上次转换到 RUNNING 状态的时间、当前时间以及 period 属性的值,该值定义了两次后续运行之间的最短时间。 如果之前的执行在时间段到期之前完成,则 ScheduledService 对象将保持 SCHEDULED 状态,直到时间段到期。 否则,如果前一次执行的时间超过指定的时间,则 ScheduledService 对象会立即转换为 RUNNING 状态。

② Task 失败

        在任务以 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。

三、代码实现

1、JavaFX 界面

在这里插入图片描述

2、创建线程池

JavaFX 多线程之 ThreadPoolExecutor 线程池

3、通过扩展 Task 类创建 Task

MultiThreadTask

import javafx.concurrent.Task;

/**
 * Task继承了java.util,concurrency.FutureTask类。
 * Task是不可以重复执行的Worker
 * 运行方式:1. new Thread(task).start();
 * 2.ExecutorService.submit(task);
 * 3.其他。
 * Task中有许多updateXXX方法用于改变相应的property。setOnXXX方法用于设置EventHandler.
 * 继承Task需要覆盖其call()方法,call()方法会在后台线程中被调用。
 * 由于java中并没有安全的线程中断机制,所以你必须在call()方法内部对isCancelled()进行判断
 *
 * @author wxhntmy
 */
public class MultiThreadTask extends Task<Void> {

    /**
     * 线程名称
     */
    private String name;

    /**
     * 构造函数
     * @param name 线程名称
     */
    public MultiThreadTask(String name) {
        this.name = name;
    }

    @Override
    protected Void call() {
        System.out.println(this.toString() + " is running!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException interrupted) {
            if (isCancelled()) {
                updateMessage("Cancelled");
            }
        }
        updateMessage("Done!");
        return null;
    }

    /**
     * 获取任务名称
     *
     * @return 任务名称
     */
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "MyTask [ name = " + name + " ]";
    }
}

启动任务:

MultiThreadTask multiThreadTask = new MultiThreadTask("new task 1");

Task<Integer> task = new Task<Integer>() {
    @Override
    protected Integer call() throws Exception {
		int iterations;
		for (iterations = 0; iterations < 10; iterations++) {
			if (isCancelled()) {
				break;
			}
			System.out.println("Iteration 2: " + iterations);
		}
		return iterations;
	}
};
executorService.submit(multiThreadTask);
//不建议显式创建线程,请使用线程池。
//new Thread(multiThreadTask).start();
executorService.submit(task);

运行结果:

my-thread-1 has been created
MyTask [ name = new task 1 ] is running!
my-thread-2 has been created
Iteration 2: 0
Iteration 2: 1
Iteration 2: 2
Iteration 2: 3
Iteration 2: 4
Iteration 2: 5
Iteration 2: 6
Iteration 2: 7
Iteration 2: 8
Iteration 2: 9

4、带返回结果的 Task

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.concurrent.Task;

/**
 * 返回部分结果的task
 *
 * @author wxhntmy
 */
public class ReturnResultTask extends Task<Object> {

    /**
     * 任务名称
     */
    private String name;

    /**
     * 只读对象包装器
     */
    private ReadOnlyObjectWrapper<String> results =
            new ReadOnlyObjectWrapper<>(this, "Results", "");

    /**
     * 获取只读对象
     * @return 只读对象
     */
    public final String getResults() {
        return results.get(); 
    }

    /**
     * 获取只读对象属性
     * @return 对象属性
     */
    public final ReadOnlyObjectProperty<String> resultsProperty() {
        return results.getReadOnlyProperty();
    }

    /**
     * 构造函数
     * @param name 任务名称
     */
    public ReturnResultTask(String name) {
        this.name = name;
    }

    @Override
    protected String call() {
        results.set("Results Get");
        try {
            Thread.sleep(100);
        } catch (InterruptedException interrupted) {
            if (isCancelled()) {
                updateMessage("Cancelled");
            }
        }
        updateMessage("Done!");
        return results.get();
    }

    @Override
    protected void succeeded() {
        super.succeeded();
        updateMessage("Done!");
        System.out.println(this.toString() + " is Done!");
    }

    @Override
    protected void cancelled() {
        super.cancelled();
        updateMessage("Cancelled!");
        System.out.println(this.toString() + " is Cancelled!");
    }

    @Override
    protected void failed() {
        super.failed();
        updateMessage("Failed!");
        System.out.println(this.toString() + " is Failed!");
    }

    @Override
    protected void running(){
        super.running();
        updateMessage("Running!");
        System.out.println(this.toString() + " is Running!");
    }

    @Override
    protected void scheduled(){
        super.scheduled();
        updateMessage("Scheduled!");
        System.out.println(this.toString() + " is Scheduled!");
    }
    
    /**
     * 获取任务名称
     *
     * @return 任务名称
     */
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "MyTask [ name = " + name + " ]";
    }
}

运行任务:

ReturnResultTask returnResultTask = new ReturnResultTask("result task");
executorService.submit(returnResultTask);

//等待returnResultTask执行完成
while (!returnResultTask.isDone()){;}
System.out.println("ReturnResultTask Get Results: " + returnResultTask.getResults());

运行结果:
在这里插入图片描述

5、绑定进度条的 Task

运行Task

Task task = new Task<Void>() {
	@Override
	public Void call() {
		final int max = 100;
		for (int i = 1; i <= max; i++) {
			if (isCancelled()) {
				break;
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException interrupted) {
				if (isCancelled()) {
					break;
				}
			}
			updateProgress(i, max);
		}
		return null;
	}
};
progressBar.progressProperty().bind(task.progressProperty());
executorService.submit(task);

运行结果:
在这里插入图片描述

6、获取 Task 返回的结果

执行任务

Task<Integer> task = new Task<Integer>() {
	@Override
	protected Integer call() throws Exception {
		int iterations;
		for (iterations = 0; iterations < 10; iterations++) {
			if (isCancelled()) {
				break;
			}
			System.out.println("Iteration 2: " + iterations);
		}
		return iterations;
	}
};
//添加Task执行成功事件
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
	@Override
	public void handle(WorkerStateEvent t) {
		//获取Task返回的值
		System.out.println("done! Get Return Value :" + t.getSource().getValue());
	}
});

executorService.submit(task);

运行结果:
在这里插入图片描述

7、通过扩展 Service 类创建 Service

import javafx.concurrent.Service;
import javafx.concurrent.Task;

/**
 * Service会封装Task,并负责后台执行它封装的Task。
 * Service是可重复执行的。
 * Service可以被注入Executor依赖,并通过注入的Executor执行实际的Task。否则Service只会构造一个简单的Thread执行。
 * Service通过Service的start()方法执行任务。
 * 可以通过WorkerStateEvent监听变化。
 * @author wxhntmy
 */
public class MultiThreadService extends Service<Object> {

    /**
     * 服务名称
     */
    private String name;

    /**
     * 构造函数
     * @param name 服务名称
     */
    public MultiThreadService(String name) {
        this.name = name;
    }

    @Override
    protected Task<Object> createTask(){

        return new Task<Object>() {
            @Override
            protected Integer call() throws Exception {
                int iterations;
                for (iterations = 0; iterations < 10; iterations++) {
                    if (isCancelled()) {
                        break;
                    }
                    System.out.println("Service Task Iteration: " + iterations);
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException interrupted) {
                    if (isCancelled()) {
                        updateMessage("Cancelled");
                    }
                }
                return iterations;
            }
        };
    }

    @Override
    protected void succeeded() {
        super.succeeded();
        System.out.println(this.toString() + " is Done!");
    }

    @Override
    protected void cancelled() {
        super.cancelled();
        System.out.println(this.toString() + " is Cancelled!");
    }

    @Override
    protected void failed() {
        super.failed();
        System.out.println(this.toString() + " is Failed!");
    }

    @Override
    protected void running(){
        super.running();
        System.out.println(this.toString() + " is Running!");
    }

    @Override
    protected void scheduled(){
        super.scheduled();
        System.out.println(this.toString() + " is Scheduled!");
    }

    /**
     * 获取服务名称
     *
     * @return 服务名称
     */
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "MyService [ name = " + name + " ]";
    }
}

运行封装的 Task

//运行方法一
//创建一个Service
MultiThreadService multiThreadService = new MultiThreadService("service");
//给Service添加Task执行成功事件
multiThreadService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent t) {
        //获取Task返回的值
        System.out.println("done! Get Return Value :" + t.getSource().getValue());
    }
});
multiThreadService.start();

//运行方法二
//创建Service,注入Executor依赖,并通过注入的Executor执行实际的Task
MultiThreadService multiThreadService2 = new MultiThreadService("service 2");
multiThreadService2.setExecutor(executorService);
multiThreadService2.getExecutor().execute(new Task<Object>() {
    @Override
    protected Integer call() throws Exception {
        int iterations;
        for (iterations = 15; iterations < 20; iterations++) {
            if (isCancelled()) {
                break;
            }
            System.out.println("Service Task 2 Iteration: " + iterations);
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException interrupted) {
            if (isCancelled()) {
                updateMessage("Cancelled");
            }
        }
        return iterations;
    }
});

运行结果:
在这里插入图片描述

8、ScheduledService 执行定时任务

通过扩展 ScheduledService 类创建 ScheduledService

import javafx.concurrent.*;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * ScheduledService是一个可以自动重新开始的任务执行器。
 * ScheduledService在fails失败时是否会自动重新开始以及自动执行的次数取决于:
 * restartOnFailure,backoffStrategy,cumulativePeriod,maximumFailureCount几个property
 * @author wxhntmy
 */
public class MultiThreadScheduledService extends ScheduledService<Object> {

    /**
     * 服务名称
     */
    private String name;

    /**
     * 计数器
     */
    private int counter = 0;

    /**
     * 构造函数
     * @param name 服务名称
     */
    public MultiThreadScheduledService(String name) {
        this.name = name;
    }

    @Override
    protected Task<Object> createTask() {
        return new Task<Object>() {
            @Override
            protected Integer call() throws Exception {
                counter++;
                Date date = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println("Now Time: " + sdf.format(date));
                System.out.println("ScheduledService Count: " + counter);
                return counter;
            }
        };
    }

    @Override
    protected void succeeded() {
        super.succeeded();
        System.out.println(this.toString() + " is Done!");
    }

    @Override
    protected void cancelled() {
        super.cancelled();
        System.out.println(this.toString() + " is Cancelled!");
    }

    @Override
    protected void failed() {
        super.failed();
        System.out.println(this.toString() + " is Failed!");
    }

    @Override
    protected void running(){
        super.running();
        System.out.println(this.toString() + " is Running!");
    }

    @Override
    protected void scheduled(){
        super.scheduled();
        System.out.println(this.toString() + " is Scheduled!");
    }

    @Override
    public void reset(){
        super.reset();
        System.out.println(this.toString() + " is Reset!");
    }

    /**
     * 获取服务名称
     *
     * @return 服务名称
     */
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "MyScheduledService [ name = " + name + " ]";
    }
}

运行Service:

MultiThreadScheduledService multiThreadScheduledService = new MultiThreadScheduledService("ScheduledService");
//上次运行开始和下一次运行开始之间允许的最短时间。
multiThreadScheduledService.setPeriod(Duration.millis(1000));
//ScheduledService 首次启动与开始运行之间的初始延迟。
multiThreadScheduledService.setDelay(Duration.millis(100));

//设置 ScheduledService 是否应在 Task 失败的情况下自动重新启动。
multiThreadScheduledService.setRestartOnFailure(false);
//ScheduledService 在简单地以 FAILED 状态结束之前可以失败的最大次数。
//multiThreadScheduledService.setMaximumFailureCount(10);

//给ScheduledService添加Task执行成功事件
multiThreadScheduledService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent t) {
        //获取Task返回的值
        System.out.println("done! Get Return Value: " + t.getSource().getValue());
    }
});
multiThreadScheduledService.start();

运行结果:

MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:43
ScheduledService Count: 1
done! Get Return Value: 1
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:44
ScheduledService Count: 2
done! Get Return Value: 2
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:45
ScheduledService Count: 3
done! Get Return Value: 3
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:46
ScheduledService Count: 4
done! Get Return Value: 4
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:47
ScheduledService Count: 5
done! Get Return Value: 5
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:48
ScheduledService Count: 6
done! Get Return Value: 6
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
MyScheduledService [ name = ScheduledService ] is Running!
Now Time: 2021-05-30 02:56:49
ScheduledService Count: 7
done! Get Return Value: 7
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
Now Time: 2021-05-30 02:56:50
ScheduledService Count: 8
MyScheduledService [ name = ScheduledService ] is Running!
done! Get Return Value: 8
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
Now Time: 2021-05-30 02:56:51
ScheduledService Count: 9
MyScheduledService [ name = ScheduledService ] is Running!
done! Get Return Value: 9
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!
Now Time: 2021-05-30 02:56:52
ScheduledService Count: 10
MyScheduledService [ name = ScheduledService ] is Running!
done! Get Return Value: 10
MyScheduledService [ name = ScheduledService ] is Scheduled!
MyScheduledService [ name = ScheduledService ] is Done!

四、示例代码

wxhntmy / JavaFX-MultiThread

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaFX 多线程之 Task、Service、ScheduledService 的相关文章

  • 快照图像不能用作舞台图标

    我正在尝试将舞台快照中的图像设置为舞台图标 下面的代码演示了它 import javafx application Application import javafx scene Scene import javafx scene imag
  • Eclipse 插件导出时出现错误:“无法解析导入 javafx.embed.swt”

    我正在尝试导出 Eclipse 插件 这只是我从此处的示例创建的一个 Eclipse 视图 http help eclipse org mars index jsp nav 2F2 0 http help eclipse org mars
  • TextField 和属性之间的 JavaFX 绑定

    如果您在 JavaFX TextField 和属性之间创建绑定 则每次击键时该绑定都会失效 从而导致文本发生更改 如果您有一系列绑定 则默认行为可能会导致问题 因为中间的编辑值可能无效 好的 我知道我可以创建从属性到文本字段的单向绑定 并注
  • JavaFX 过渡动画等待

    这么快 我正在做一个演示计算机图形绘制方法的程序 我需要创建时间线或操作历史记录 例如 placeVertex x y moveVertex newX newY 等 并迭代 向前和向后 自动或手动 我已经通过使用命令设计模式实现了这一点 但
  • JavaFX 滚动表更新性能随着时间的推移而降低

    我有一个 TableView 显示最后 N 个项目 顶部的新项目 从底部删除项目等 似乎发生的情况是 CPU 负载随着时间的推移而增加 导致同一台计算机上的其他 X 应用程序变得缓慢 平台详细信息 Redhat 6 7 32 位 Java
  • JavaFX ComboBox OnChangeListener 回滚更改

    我正在尝试重置 a 的选择ComboBox如下 private ListView
  • 在胶子mapLayer中创建折线

    Google 地图 API 可以在地图上创建包含连接点的折线的图层 我搜索了在哪里可以找到 gluon 的 mapLayer 的示例或实现 请指教 虽然没有明确的 API 用于在对象之上绘制直线 折线或多边形MapView the MapL
  • 如何使ListView的宽度适合其单元格的宽度

    我有一个带有自定义单元工厂的 ListView 我计算单元格的最大值 minWidth 并将其设置为整个 ListView 的 minWidth 因此 受布局中其他区域约束的 ListView 会精确地缩小到 minWidth 但它所容纳的
  • 带有对象的 Javafx 可编辑组合框

    我刚刚开始学习 Java Fx 我有一个装满物体的组合框 我处理了 toString 方法 我可以看到我想要在屏幕上显示的名称 但现在我想让它可编辑 用户将输入自己的文本 ComboBox 将创建一个新对象并将该文本放入正确的字段中 我知道
  • JavaFX 中的隐形舞台/场景

    我正在寻找一种隐藏 JavaFX 舞台或场景的方法 现在我知道了 hide 但这行不通 我需要一些仍然保留窗口的东西 但只是使其完全透明 一个很好的比喻是display none and visibility hidden在CSS中 第一个
  • JavaFX Node.lookup() 仅对使用 FXMLLoader 加载的 Parent 中的某些元素返回 null

    我从 FXML 加载了一个父级 将其添加到场景 舞台内的窗格中并显示它 然后立即查找组件 Lookup 对某些返回 null 但对其他返回非 null 什么情况下会这么做 这是加载和查找代码 rootUi FXMLLoader load g
  • JavaFX 中按下按钮的样式

    我有一个Button in my FXML文件 我在下面给它一个样式CSS button fx background color linear gradient ff5400 be1d00 fx background radius 30 f
  • 如何根据鼠标位置显示工具提示? - JavaFX

    我有一个stackPane 充满一个圆圈和几条线 我想在将鼠标悬停在 StackPane 上时显示工具提示 并且工具提示应包含X Y coords鼠标的 我知道如何获取鼠标的坐标 但我无法找到显示工具提示的方法 你们中有人能帮我吗 安舒尔
  • Javafx-场景快照不显示值和系列

    我制作了一个非常短的应用程序 它使用 javafx 生成图表 应用程序显示正确的值 图表 但当我做快照时 图像仅显示轴和标签 但不显示系列和值 stage setTitle Line Chart Sample final DateAxis
  • 如何使用 Java 11 和 JavaFX 11 运行 ControlsFX 示例应用程序

    ControlFX 网站 http fxexperience com controlsfx says 如果您想使用 ControlsFX 示例应用程序 只需 下载 ControlsFX 版本并在上运行以下命令 命令提示符 请务必将 替换为实
  • 如何使用 JavaFX 中的 JCSG 库将 MeshView 转换为 CSG 对象

    我正在使用 JavaFX 的 JCSG 库 我有一些MeshView我想将它们转换成的对象CSG对象 有办法实现吗 最简单的方法是组合javafx scene shape Mesh对象与 CSG 对象 前提是您有TriangleMesh正在
  • 如何使 AnchorPane 大小响应最大化的屏幕视图

    我不知道什么是灰色地带以及如何扩展我的Anchorpane到整个屏幕Image https i stack imgur com m5HqA png 不是未最大化的视图是这样的image2 https i stack imgur com 14
  • JavaFX中如何获取鼠标位置?

    我是java fx 的初学者 如何在 JavaFX 中获取鼠标在 x 和 y 中的位置 我尝试使用 AWTMouseInfo 也导入了它 但它不起作用 我还在 Ensembles 中看到了它的代码 在 高级阶段 拖动球窗口 这就是我需要做的
  • 如何根据 JavaFX 8 中的特定祖先获取节点边界?

    我在 AnchorPane 中添加了一个图表 我想获取其绘图的范围 图表 我用青色标记 这样我就可以在它上面添加一些文本 但我应该根据它的祖先知道它的确切边界 如果我手动执行此操作 则在调整大小等时更改节点的填充大小时 我可能会失败 imp
  • 在 JavaFX 中搜索 TableView 列表

    如何在 TableWie 中查找记录 例如通过 ID 并选择创建的行并将其放在 Java 8 JavaFX 中的屏幕中间 您可以使用以下方式搜索元素 int searchId table getItems stream filter ite

随机推荐

  • rm刷新了我的认知

    大家好 我是良许 创建 删除和修改文件是用户在 Linux 系统中执行的非常常见操作 大家都知道 在 Linux 系统里使用 rm 命令删除单个文件时 几乎一瞬间就完成了 但是如果文件数量很大 那么删除操作就需要很长时间才能完成 你有没想过
  • mdl格式是什么软件生成的_【原创】slx文件与mdl文件的差别

    slx文件是二进制格式文件 mdl文件是文本格式文件 早期Simulink的模型文件为mdl格式 slx格式在Matlab Simulink R2012a版本中引入 旨在取代以前的mdl格式 由于slx被压缩 通常文件大小会比相同的mdl文
  • 2023跨年烟花(浪漫烟花+自定义音乐+雪花飘飘+自定义文字)

    效果 介绍 可以自定义跨年文字 可自定义更换背景音乐 具有雪花效果 项目文件index html gameCanvas 4 0 js script js index html文件
  • SQL select into from 和 insert into from 语句

    在刷笔试题的时候看到一个表的复制的问题 以前没见过 写一下 用于创建表的备份复件 复制表 两条SQL语句 1 insert into tableName2 column name1 column name2 select column na
  • >> << & 操作符总结

    lt lt 符号 扩大多少倍 Integer num 16 num num lt lt 2 System out println num gt gt gt gt gt num gt gt 符号 缩小多少倍 Integer num 16 nu
  • 沈师 PTA 数据库题目及部分解析 第五章

    判断题 1 当主外健不能保证参照完整性和数据一致性时 还可以选择触发器来实现 T 2 域完整性是保证数据完整性的重要约束之一 T 3 当用户违反了参照完整性时 DBMS会采取一些策略加以处理 包括拒绝 级联操作和设置为空值 T 4 参照完整
  • m1 mac安装docker、k8s(成功案例)

    m1 mac安装docker k8s 成功案例 一 安装docker 二 安装kubernetes ps 系统 macos 12 monterey 一 安装docker 1 先要清理旧数据 讲道理这一步重不重要我还真没试过 毕竟在docke
  • 问题 C: 销售排行榜

    题目描述 你的任务是帮助淘宝网店店长整理销售数据 根据累计的销售记录 将所有商品按销售数量降序排列 输入 输入包括多行数据 行数小于100000 每行数据包括4个信息 分别是商品名称 销售数量 单价 成交日期 商品名称由小写字母组成 且不超
  • Android JNI

    选择你的到处路径和名称JAR file 直接Finish 这样的jar包里面只包括编译后的AdMob class 很小很干净 然后打开羽化的Unity工程AdMobTest File gt Open Project 双击UnityAdMob
  • Linux下安装ORACLE(一周成果、全是干货!)

    文章目录 一 安装前言 1 闲聊 2 提前知道的事 二 安装准备 1 Linux发行版本 ret hat7 4 更换yum源 传送门 https blog csdn net qq 42956179 article details 10961
  • 学习笔记:MongoDB命令的基础学习,使用springboot实现mongodb视图创建

    学习笔记 MongoDB命令的基础学习 使用springboot实现mongodb视图创建 创建集合 db createCollection my 查看集合 show collections 删除集合 db my drop 插入一条 没有c
  • Python描述符是什么?

    在Python中 通过使用描述符 程序员可以在引用对象属性时定制要完成的工作 接下来我们一起来聊聊Python描述符相关的知识 本质上 描述符是一个类 但它定义了另一个类中属性的访问模式 换句话说 一个类可以将属性管理全权委托给描述符类 描
  • 计算机视觉各领域前沿算法积累

    目录 一 目标检测 1 SSD 2 PaddleDetection 3 mmdetection 4 mediapipe 二 单目标跟踪 1 pytracking 2 pysot 3 SiamMask 4 siammask e 5 Alpha
  • MATLAB神经影像学数据的多模态多元模式分析方法

    在过去的十年中 多变量模式分析的使用在功能磁共振成像 fMRI 研究中变得流行 这并不奇怪 因为 MVPA 与传统且更常用的单变量分析相比具有几个优势 首先 MVPA 可以比单变量方法在区分感兴趣的条件方面提供更高的灵敏度 因为它考虑了体素
  • js栈内存和堆内存与垃圾回收机制

    v8作为javascirpt引擎 对变量的存储主要有两种位置 栈存储和堆存储 栈内存存储基本类型的变量 如Number String boolen undefined null 以及对对象和数组变量的指针 对象是动态分配内存 数组的初始化方
  • 《深入理解计算机系统》怎样学?个人看法和资源分享

    前言 我与 深入理解计算机系统 有三次接触吧 真有缘 第一次 在我读专科大二的时候 自己就看过一遍 深入理解计算机系统 想想就是放寒假前看完的 和现在的时间也差不了多少 第二次 在专科大三下学期又重新重头看起了本书 其主要原因为第一遍看的比
  • [面试题] 递增序列生成

    给定一个表达式 f i j 2 i 5 j i 和 j 为正整数 使得f i j 严格递增 求这个递增序列 思路 和 剑指OFFER 中求丑数的题目很像 序列中第N个数 必然是是前N 1个数乘以 2 或者乘以5 中大于第N 1个数的最小值
  • 高并发场景下的限流策略

    高并发场景下的限流策略 性能调优是针对于代码本身的不规范性和系统资源的瓶颈的 当计算机的硬件资源达到瓶颈的时间已经无法调优了 高并发场景下一方面通过缓存 异步化 服务化 集群去增加整个系统的吞吐量 另一方面通过限流 降级来保护系统 什么是限
  • 机器学习D14——随机森林

    随机森林 是bagging装袋法的代表 弱学习器只可以是决策树 简介 随机森林是一种有监督学习算法 是以决策树为基学习器的集成学习算法 随机森林非常简单 易于实现 计算开销也很小 在分类和回归上表现出非常惊人的性能 因此 随机森林被誉为 代
  • JavaFX 多线程之 Task、Service、ScheduledService

    JavaFX 多线程之 Task Service ScheduledService 一 开发环境 二 javafx concurrent 包 1 Worker 接口 2 Task 类 取消 Task 显示后台 Task 的进度 3 Serv