前言
JavaFX是单线程编程,怎么个意思呢?意思就是所有对界面的操作都会交给唯一的线程Application Thread去处理,比如你要修改按钮一的名称,同时又要添加个按钮二,然后还要干点别的,那么不好意思,操作界面的线程只有一个,大家排队一个一个来。
1、我为什么要在JavaFX中使用多线程编程?
答:其实我主要是为了解决很多时候的卡顿问题(这个本应该说是用延迟加载来解决,但延迟加载离不开多线程思想)。
比如:打开一个页面同时加载数据会很卡(因为加载数据紧随页面初始化的动作)这样给人的体验很不友好,怎么解决呢,网上很多人都会用javafx.concurrent包下的Task来实现,这是一种解决方案。
但开始时我说了我主要是为了解决卡顿问题,那么既然卡顿是因为某两个动作连续执行造成的,那么我们可以将后面的动作延迟一下,同样可以解决卡顿的问题,比如我们先让页面初始化,接着延迟1秒来加载数据,所以时间轴动画也可以用来实现某些场合下的多线程编程效果。
当然多线程还能解决很多问题,比如一个很耗时的网络请求等等。
2、为什么把延迟加载跟多线程放一起说
答:因为延迟加载离不开多线程的支持。
3、实例-1:使用Platform.runLater(...)实现多线程
javafx.application包下的亲儿子,原理是把你要执行的动作添加到类似队列中,等Application Thread线程空闲时处理,说到底还是主线程(Application Thread)处理的,你的那个线程只负责给等待队列扔了一下。
下面用两个按钮来演示多线程(其实下面这种情况没必要用多线程,只是为了举例),一个演示错误用法,一个演示正确用法。
package zkh.javafx.learn.concurrent;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* 多线程
*/
//继承javafx.application.Application是JavaFX的开始
public class Concurrent1 extends Application {
/**
* Stage:就是你能看到的整个软件界面(窗口)
* Scene:就是除了窗口最上面有最大、最小化及关闭按钮那一行及窗口边框外其它的区域(场景)
* 场景(Scene)是一个窗口(Stage)必不可少的
*/
@Override
public void start(Stage stage) throws Exception {
VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER);
final Button button1 = new Button("我是按钮1");
button1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Thread button1thread = new Thread() {
@Override
public void run() {
// 当单击这个按钮时会报异常,但如果你切换下窗口按钮名称可能已经改变,但当时直接就会报错啊,所以是错误用法
// Exception in thread "button1thread" java.lang.IllegalStateException: Not on FX application thread; currentThread = button1thread
button1.setText("我现在是按钮100");
}
};
button1thread.setName("button1thread");
button1thread.start();
}
});
final Button button2 = new Button("我是按钮2");
button2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Thread button2thread = new Thread() {
@Override
public void run() {
// 这个是正确的用法
Platform.runLater(new Runnable() {
public void run() {
button2.setText("我现在是按钮200");
}
});
}
};
button2thread.setName("button2thread");
button2thread.start();
}
});
vBox.getChildren().addAll(button1, button2);
// 1、初始化一个场景
Scene scene = new Scene(vBox, 800, 600);
// 2、将场景放入窗口
stage.setScene(scene);
// 3、打开窗口
stage.show();
}
public static void main( String[] args ){
// 启动软件
Application.launch(args);
}
}
效果动图:
4、实例-2:使用javafx.concurrent包下的Task实现多线程
网上都说Task能实现多线程编程,按我的想法就是直接在Task的call方法里写进修改按钮名称的代码(button1.setText("我的新名字是100");)然而报错了,所以曲线救国在Task的call方法里更新Task的属性message(用这个存放我按钮的新名字),然后将它绑定到按钮名称text属性,就这么实现了”多线程“(可能是吧,这操作不是我想象)。
package zkh.javafx.learn.concurrent;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* 多线程
*/
//继承javafx.application.Application是JavaFX的开始
public class Concurrent2 extends Application {
/**
* Stage:就是你能看到的整个软件界面(窗口)
* Scene:就是除了窗口最上面有最大、最小化及关闭按钮那一行及窗口边框外其它的区域(场景)
* 场景(Scene)是一个窗口(Stage)必不可少的
*/
@Override
public void start(Stage stage) throws Exception {
VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER);
final Button button1 = new Button("我是按钮1");
button1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Task<Void> task = new Task<Void>(){
@Override
protected Void call() throws Exception {
// 我这样用的时候报错了
// button1.setText("我的新名字是100");
// (1)正确用法,更新message属性值
updateMessage("我的新名字是100");
return null;
}
};
// (2)将task的message属性值绑定到button1的text(名称)属性值
button1.textProperty().bind(task.messageProperty());
// 创建线程并开启
new Thread(task).start();
}
});
vBox.getChildren().addAll(button1);
// 1、初始化一个场景
Scene scene = new Scene(vBox, 800, 600);
// 2、将场景放入窗口
stage.setScene(scene);
// 3、打开窗口
stage.show();
}
public static void main( String[] args ){
// 启动软件
Application.launch(args);
}
}
效果动图:
5、实例-3 利用Timer+Platform.runLater(...)来实现延迟加载
延迟2000毫秒将button1的名字改为"我的新名字是100"
package zkh.javafx.learn.concurrent;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* 多线程
*/
//继承javafx.application.Application是JavaFX的开始
public class Concurrent3 extends Application {
/**
* Stage:就是你能看到的整个软件界面(窗口)
* Scene:就是除了窗口最上面有最大、最小化及关闭按钮那一行及窗口边框外其它的区域(场景)
* 场景(Scene)是一个窗口(Stage)必不可少的
*/
@Override
public void start(Stage stage) throws Exception {
VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER);
final Button button1 = new Button("我是按钮1");
button1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// 延迟2000毫秒将button1的名字改为"我的新名字是100"
new Timer().schedule(new TimerTask() {
public void run() {
Platform.runLater(new Runnable() {
public void run() {
button1.setText("我的新名字是100");
}
});
}
}, 2000);
}
});
vBox.getChildren().addAll(button1);
// 1、初始化一个场景
Scene scene = new Scene(vBox, 800, 600);
// 2、将场景放入窗口
stage.setScene(scene);
// 3、打开窗口
stage.show();
}
public static void main( String[] args ){
// 启动软件
Application.launch(args);
}
}
效果动图:
6、实例-4 利用时间轴动画(Timeline)来实现延迟加载
package zkh.javafx.learn.concurrent;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* 多线程
*/
//继承javafx.application.Application是JavaFX的开始
public class Concurrent4 extends Application {
/**
* Stage:就是你能看到的整个软件界面(窗口)
* Scene:就是除了窗口最上面有最大、最小化及关闭按钮那一行及窗口边框外其它的区域(场景)
* 场景(Scene)是一个窗口(Stage)必不可少的
*/
@Override
public void start(Stage stage) throws Exception {
VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER);
final Button button1 = new Button("我是按钮1");
button1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// (我喜欢用这个方式来延迟)延迟2000毫秒将button1的名字改为"我的新名字是100"
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
timeline.setAutoReverse(false);
KeyFrame keyFrame = new KeyFrame(Duration.millis(2000), new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
button1.setText("我的新名字是100");
}
});
timeline.getKeyFrames().clear();
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
});
vBox.getChildren().addAll(button1);
// 1、初始化一个场景
Scene scene = new Scene(vBox, 800, 600);
// 2、将场景放入窗口
stage.setScene(scene);
// 3、打开窗口
stage.show();
}
public static void main( String[] args ){
// 启动软件
Application.launch(args);
}
}
效果动图(跟实例-3效果一致):
7、源代码(不推荐,文章基本都有了)
JavaFX+Jfoenix 学习笔记(七)--多线程、延迟加载源码
好了,多看注释!