本文是java图形界面入门系列最后一篇,swing和javafx各十篇,本系列文章的阅读顺序有先后之分。学习完本系列之后,仅仅是对java开发图形界面有了个基本认识,可以自行学习其余知识。本站之后更新的相关文章将不再有阅读顺序,可自行选择阅读。本系列javafx部分文章的所有代码都会在这里更新,本系列关于swing部分的代码在这里更新。

javafx.concurrent包由Worker接口和两个具体的实现Task和Service类组成。Worker接口提供了对后台工作与UI之间通信有用的API。Task类是java.util.concurrent.FutureTask类的一个完整的可观察的实现。Task类使开发者可以在JavaFX应用程序中实现异步任务。Service类执行这些任务。

Worker接口
定义了一个执行一个或多个后台线程工作的对象,Worker对象的状态在JavaFX应用程序线程中是可观察且可用的。

Task类
用来实现需要在后台完成的工作的逻辑。首先你需要扩展Task类。实现Task的类必须重写call方法来处理后台工作和返回结果。

public class TestThread extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Group group = new Group();
        Scene scene = new Scene(group);
        stage.setScene(scene);
        Button button = new Button("按钮");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                Task task = new Task() {
                    @Override
                    protected Object call() throws Exception {
                        Thread.sleep(3000);
                        System.out.println(Thread.currentThread().getName());
                        return null;
                    }
                };
                Thread th = new Thread(task);
                th.setDaemon(true);
                //启动线程
                th.start();
                System.out.println("新创建的线程: " + th.getName());
            }
        });
        HBox hBox = new HBox(button);
        hBox.setPadding(new Insets(15, 12, 15, 12));//节点到边缘的距离
        hBox.setSpacing(10);   //节点之间的间距
        hBox.setStyle("-fx-background-color: #336699;");//背景颜色
        hBox.setPrefWidth(400);
        hBox.setPrefHeight(200);
        hBox.setAlignment(Pos.CENTER);
        group.getChildren().add(hBox);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

上面的示例实现了一个简单的task,通常我们需要往task中传递参数,此时我们可以声明一个常量,在task中使用这个常量。但是最好的方法时自己定义一个task子类,将常量作为该类的成员变量。上面的代码可以改写成:

public class TestThread2 extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Group group = new Group();
        Scene scene = new Scene(group);
        stage.setScene(scene);
        Button button = new Button("按钮");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                Task<Object> task = new MyTask(1000);
                Thread th = new Thread(task);
                th.setDaemon(true);
                //启动线程
                th.start();
                System.out.println("新创建的线程: " + th.getName());
            }
        });
        HBox hBox = new HBox(button);
        hBox.setPadding(new Insets(15, 12, 15, 12));//节点到边缘的距离
        hBox.setSpacing(10);   //节点之间的间距
        hBox.setStyle("-fx-background-color: #336699;");//背景颜色
        hBox.setPrefWidth(400);
        hBox.setPrefHeight(200);
        hBox.setAlignment(Pos.CENTER);
        group.getChildren().add(hBox);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class MyTask extends Task<Object> {
    private final Integer time;

    public MyTask(Integer time) {
        this.time = time;
    }

    @Override
    protected Object call() throws Exception {
        Thread.sleep(time);
        System.out.println(Thread.currentThread().getName());
        return null;
    }
}

注意:不要将一个引用对象传递给task,并在task中更新这个引用对象。尽管task子类中使用了final关键字,但是对引用对象的属性的更新操作并不安全。
对于CRUD任务,人们预计“创建”任务将返回新创建的对象或主键,“读”任务将返回读对象,“更新”任务将返回已更新的记录数,而“删除”任务将返回已删除的记录数。有时候不需要返回的时候,可以把call方法的返回值类型定义为Void,注意是Void而不是void,例如:

class MyTask extends Task<Object> {
    private final Integer time;

    public MyTask(Integer time) {
        this.time = time;
    }

    @Override
    protected Void call() throws Exception {
        Thread.sleep(time);
        System.out.println(Thread.currentThread().getName());
        return null;
    }
}

如果需要在task中渲染UI组件,那么可以使用下面的方式:

button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                Task<Object> task = new Task<Object>() {
                    @Override
                    protected Object call() throws Exception {
                        System.out.println("线程1:" + Thread.currentThread().getName());
                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                //此处渲染UI组件
                                System.out.println("线程2:" + Thread.currentThread().getName());
                            }
                        });
                        return null;
                    }
                };
                Thread th = new Thread(task);
                th.setDaemon(true);
                //启动线程
                th.start();
                System.out.println("线程3:" + Thread.currentThread().getName());
            }
        });

运行结果:

线程3:JavaFX Application Thread
线程1:Thread-3
线程2:JavaFX Application Thread

可以发现,使用Platform.runLater方法执行的代码都是在JavaFX Application Thread线程中执行的。而JavaFX Application Thread正是我们创建UI组件使用的线程。

Service类
Service类是设计来在一个或多个后台线程上执行一个Task对象。Service类的方法和状态必须只可以由JavaFX应用程序线程访问(第一次启动可以不是该线程)。

public class ProgressSample3 extends Application {

    final Service<Integer> service = new ProgressService<Integer>();

    @Override
    public void start(Stage stage) {
        Group root = new Group();
        Scene scene = new Scene(root, 300, 250);
        stage.setScene(scene);
        stage.setTitle("下载文件进度条同步更新");

        Label label = new Label("下载进度:");
        ProgressBar progressBar = new ProgressBar();
        progressBar.setProgress(0);
        progressBar.setPrefWidth(200);
        HBox hBox = new HBox();
        hBox.setAlignment(Pos.TOP_CENTER);
        hBox.setPrefHeight(60);
        hBox.getChildren().addAll(label, progressBar);

        Button button = new Button("开始下载");
        button.setOnMouseClicked((e) -> {
            progressBar.progressProperty().bind(service.progressProperty());
            //JavaFX Application Thread
            service.restart();
        });

        VBox vBox = new VBox();
        vBox.setSpacing(5);
        vBox.setAlignment(Pos.CENTER);
        vBox.getChildren().addAll(hBox, button);
        scene.setRoot(vBox);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class ProgressService<Integer> extends Service<Integer> {
    @Override
    protected Task<Integer> createTask() {
        //JavaFX Application Thread
        return new ProgressTask<Integer>();
    }
}

class ProgressTask<Integer> extends Task<Integer> {
    @Override
    protected Integer call() throws Exception {
        System.out.println("新创建的线程: " + Thread.currentThread().getName());
        int i = 0;
        while (i++ < 100) {
            updateProgress(i, 100);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
//                e.printStackTrace();
            }
        }
        return null;
    }
}

Q.E.D.


擅长前端的Java程序员