JavaFX 中使用多线程与保证 UI 线程安全
JavaFX 中使用多线程与保证 UI 线程安全
- JavaFX 中使用多线程
- JavaFX 中保证 UI 线程安全
- 总结与补充
JavaFX 中的 UI 线程和大多数其它的编程语言一样,是单线程的。前人很早就已经多次尝试在 UI 线程上使用多线程,大多都已失败告终。为保证 UI 界面的流畅,UI 线程不能执行非常耗时的操作。如果 UI 线程执行正在非常耗时的操作,这个后果在 UI 界面的体现就是,UI 界面会一直停滞在执行耗时代码前的状态,然后如果马上随意连续点击 UI 界面的任何部位,此时会发生如下现象:
应用的标题会加上
(未响应)
的后缀。应用的关闭按钮会变红。
光标位于此应用中时,光标会变成加载的圆圈图样。
操作系统会将此应用的界面变成灰色,然后弹窗提示此程序已停止响应。
上面的就是俗称应用卡死的状态。通过上面的描述应该可以明白,不是说一旦进入卡死的状态,就只能手动强制结束这个应用。应用卡死的状态只能一种 UI 界面被阻塞的状态(UI 界面无法自主更新)。当发生了这个状态,并不能说明程序就发生了死锁,因此此时如果等待,程序就有可能自主走出这个状态。只能说这个应用的开发者的设计不合理,UI 线程不应该执行非常耗时的操作。那么,非常耗时的操作应该在哪里执行呢?
【注意】
UI 界面的更新是以异步的方式进行。UI 线程首先会执行用户代码,然后如果这些代码使得 UI 界面的数据发生的改变,UI 线程将对其 UI 界面进行更新。这意味着,并不是每执行一条更改 UI 数据的代码,它都会在 UI 界面上马上生效。有时候,这会导致一些问题。
UI 线程是单线程的,指的 UI 界面是只通过一个线程来完成它界面的更新,指的不是凡是涉及 UI 的程序只能使用一个线程。UI 应用相比于后台应用,只是多了几个与处理 UI 相关的线程而已,没什么额外的线程个数限制。
如果想了解更多关于同步、异步、阻塞的知识,可见笔者的另一篇博客:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞:https://blog.csdn.net/wangpaiblog/article/details/117236684
JavaFX 中使用多线程
为防止 UI 界面被阻塞,又因为 UI 线程是单线程的,因此应该选择在其它线程执行非常耗时的操作。可以选择当需要执行非常耗时的操作时,新开一个线程,将此非常耗时的操作放到新开一个线程去执行。
在 JavaFX 中使用多线程一般使用两个类:ExecutorService
、Task<Integer>
。Task<Integer>
有一个方法 call
,可以在这个方法去执行耗时操作。具体代码如下:
// 假设方法 someJavafxFun 位于 JavaFX 的某个组件的定义中
public void someJavafxFun() {ExecutorService executor = Executors.newCachedThreadPool();Task<Integer> task = new Task<>() {@Overrideprotected Integer call() {// TODO 执行耗时操作return null; // 如果需要结果反馈,可以在此处提供反馈值}};/*** 如果不需要结果反馈,也可以直接使用 executor.execute(task);* * 可以使用 result.get() 来获取上面的反馈值。但这个方法是同步阻塞的*/var result = executor.submit(task);/*** 方法 getWindow() 获得的其实是 Stage。此段代码是用于在应用关闭时回收资源。* * 对于真正的程序,方法 setOnCloseRequest 要设置在 Stage 被创建处。* 因为方法 setOnCloseRequest 会覆盖其它 setOnCloseRequest 的效果,所以此方法只能执行一次。* 为了达到这个效果,需要将 task 与 executor 设置成全局的,或者将其封装在一个全局静态方法中*/this.getScene().getWindow().setOnCloseRequest(event -> {if (task != null) {task.cancel();}if (executor != null) {executor.shutdown();}Platform.exit();});
}
Task<T>
是 JavaFX 的一个类,它继承至 FutureTask<T>
。而 FutureTask<T>
、ExecutorService
均为原生的 Java 多线程中的类,后续的操作均可依照 Java 多线程理论中的流程来完成。
【附】
可以使用 Thread.currentThread().getName()
来查看某代码位于的线程。如下。其中,JavaFX 的 UI 界面所在的线程名为 JavaFX Application Thread
。
System.out.println("【编号xxx】 执行本代码 XXX 的线程是:" + Thread.currentThread().getName());
JavaFX 中保证 UI 线程安全
JavaFX 中的 UI 和大多数其它的编程语言中的一样,不是线程安全的,因为它是单线程的。在单线程中无需考虑线程安全的问题,但在多线程中需要考虑。介于本文讨论的重点,这里不打算解释什么是线程安全。那么,如果在 JavaFX 中使用了多线程,如何保证 UI 线程安全呢?
在 JavaFX 中,可以在 UI 之外的线程中,使用方法 Platform.runLater
来执行与 UI 直接相关的操作。如下:
Platform.runLater(() -> {/* // TODO 更新 UI 数据的代码 */});
注意:为保证 UI 界面的流畅,只需将与 UI 直接相关的代码置入上述的方法 Platform.runLater
中,不要在此方法中放多余的代码,否则就失去了使用多线程的意义。
总结与补充
为保证 UI 界面的流畅,需要将某些代码放入新建线程中,这些代码需要同时满足以下条件:
非常耗时或执行时间不能保证最坏结果也符合要求
与操作 UI 数据不直接相关
并非与 UI 数据强同步。
例如:如果 UI 需要请求一个资源,如果该资源不能获得,UI 就会崩溃,那么获取该资源的代码不能放在新建线程中,除非可以保证此线程与 UI 线程可以同步。因为,在不使用任何机制的情况下,新建的线程都是非阻塞的,如果选择将获取该资源的代码放在新建线程中,在这种情况下,UI 中请求资源的方法会立即返回,这个时候获取到的是这个资源的初始值(一般是 null)。也就是说,如果选择将获取该资源的代码放在新建线程中,相当于直接注释掉了新建线程获取资源这部分的代码。
为保证 UI 的线程安全,在其它线程不能直接更改 UI 的数据,必须将更改 UI 数据的代码传于方法
Platform.runLater
中运行。
JavaFX 中使用多线程与保证 UI 线程安全相关推荐
- 线程中如何使用对象_在 Flink 算子中使用多线程如何保证不丢数据?
简介: 本人通过分析痛点.同步批量请求优化为异步请求.多线程 Client 模式.Flink 算子内多线程实现以及总结四部分帮助大家理解 Flink 中使用多线程的优化及在 Flink 算子中使用多线 ...
- udp怎么保证不丢包_在 Flink 算子中使用多线程如何保证不丢数据?
分析痛点 笔者线上有一个 Flink 任务消费 Kafka 数据,将数据转换后,在 Flink 的 Sink 算子内部调用第三方 api 将数据上报到第三方的数据分析平台.这里使用批量同步 api,即 ...
- C++ 中的多线程的使用和线程池建设。150行代码,手写线程池
C++ 11 引入了 std::thread 标准库,方便了多线程相关的开发工作. 说到多线程开发,可不仅仅是创建一个新线程就好了,不可避免的要涉及到线程同步的问题. 而保证线程同步,实现线程安全,就 ...
- java actor和线程有什么区别_java – 在Akka Actors中使用OpenGL:保证单个线程用于特定的actor...
我有一个Scala / Java OpenGL应用程序,我在其中使用Akka框架.目前,我的OpenGL线程独立于actor系统,因此我可以保证对OpenGL函数的调用始终来自单个线程.这很重要,否则 ...
- 【Boost】boost库中thread多线程详解13——线程标识符
在boost中也有唯一标识线程的数据结构:thread::id. boost::thread thread_func(func); thread::id var_id = thread_func.ge ...
- 【Boost】boost库中thread多线程详解12——线程的分离与非分离
Boos::thread线程的默认属性为非分离状态,线程结束后线程标识符.线程退出状态等信息需要通过join方法回收. boost::thread thread_func(func); thread_ ...
- 【Boost】boost库中thread多线程详解11——线程的休眠和中断
boost::thread 中提供一个静态方法 void boost::thread::sleep(system_time const& abs_time); 线程将休眠直到时间超时. sle ...
- 【Boost】boost库中thread多线程详解6——线程组简单例子
如果你需要创建几个线程,考虑使用一个线程组对象thread_group来组织它们.一个thread_group对象可以使用多种方法管理线程.首先,可以使用一个指向动态创建的线程对象的指针作为参数来调用 ...
- python线程卡死问题解决_Python中的多线程:最后一个线程卡住了
我遇到了一个奇怪的情况,在经历了很多次试射后我还是搞不清楚.我使用多线程(10)来读取url(100),它在大多数情况下都能正常工作,但在某些情况下,它会在最后一个线程上卡住.我等待它看它是否返回,花 ...
最新文章
- Android的消息机制
- 手把手教你在Linux上搭建BitTorrent服务器
- Linkify介绍 编辑textview超链接
- MapReduce将小文件合并成大文件,并设置每个切片的大小的案例
- This dependency was not found: * !!vue-style-loader!css-loader?……
- json文本装换为JSONArray
- QJsonObject写入读出json文件中文乱码问题解决
- mysql-电商库演练1-创建数据-基本查询练习
- oracle常用系统变量,Oracle环境变量
- 《Python学习笔记》——南溪的python编程笔记
- android电池(五):电池 充电IC(PM2301)驱动分析篇
- mysql 不允许保存修改_解决SQL SERVER 2014 不允许保存更改。您所做的更改要求删除并重新创建以下表。您对无法重新创建的表进行了更改或者启用了阻止保存要求重新创建表的更改选项...
- SQL注入:SQL注入防御
- 服装行业ERP体系的主要好处
- SpringCloudGateway原理——Gateway集成eureka服务发现转发请求
- C/C++标准库之numeric
- 安卓证书免费在线制作工具
- 宿舍管理系统1(pyhon与mysql)
- 永恒之蓝(Eternal Blue)复现
- 用Liveupdata 刷MSI主板Slic 2.1
热门文章
- 【Hibernate】getHibernateTemplate与getSession的区别以及优缺点
- 网上商城—管理员增加商品
- Data Vault的一点思考(二)
- Pandas 基础(9) - 组合方法 merge
- 2017云栖大会·杭州峰会:《在线用户行为分析:基于流式计算的数据处理及应用》之《数据可视化:构建实时动态运营数据分析大屏》篇...
- [华为机试练习题]5.IP地址推断有效性
- springmvc中的全注解模式
- ENode 2.0 - 深入分析ENode的内部实现流程和关键地方的幂等设计
- 数据仓库专题(6)-数据仓库、主题域、主题概念与定义
- Python-读取文件:API介绍