JavaFX 中使用多线程与保证 UI 线程安全
JavaFX 中使用多線程與保證 UI 線程安全
- JavaFX 中使用多線程
- JavaFX 中保證 UI 線程安全
- 總結(jié)與補(bǔ)充
??JavaFX 中的 UI 線程和大多數(shù)其它的編程語言一樣,是單線程的。前人很早就已經(jīng)多次嘗試在 UI 線程上使用多線程,大多都已失敗告終。為保證 UI 界面的流暢,UI 線程不能執(zhí)行非常耗時(shí)的操作。如果 UI 線程執(zhí)行正在非常耗時(shí)的操作,這個(gè)后果在 UI 界面的體現(xiàn)就是,UI 界面會(huì)一直停滯在執(zhí)行耗時(shí)代碼前的狀態(tài),然后如果馬上隨意連續(xù)點(diǎn)擊 UI 界面的任何部位,此時(shí)會(huì)發(fā)生如下現(xiàn)象:
-
應(yīng)用的標(biāo)題會(huì)加上 (未響應(yīng)) 的后綴。
-
應(yīng)用的關(guān)閉按鈕會(huì)變紅。
-
光標(biāo)位于此應(yīng)用中時(shí),光標(biāo)會(huì)變成加載的圓圈圖樣。
-
操作系統(tǒng)會(huì)將此應(yīng)用的界面變成灰色,然后彈窗提示此程序已停止響應(yīng)。
??上面的就是俗稱應(yīng)用卡死的狀態(tài)。通過上面的描述應(yīng)該可以明白,不是說一旦進(jìn)入卡死的狀態(tài),就只能手動(dòng)強(qiáng)制結(jié)束這個(gè)應(yīng)用。應(yīng)用卡死的狀態(tài)只能一種 UI 界面被阻塞的狀態(tài)(UI 界面無法自主更新)。當(dāng)發(fā)生了這個(gè)狀態(tài),并不能說明程序就發(fā)生了死鎖,因此此時(shí)如果等待,程序就有可能自主走出這個(gè)狀態(tài)。只能說這個(gè)應(yīng)用的開發(fā)者的設(shè)計(jì)不合理,UI 線程不應(yīng)該執(zhí)行非常耗時(shí)的操作。那么,非常耗時(shí)的操作應(yīng)該在哪里執(zhí)行呢?
【注意】
??UI 界面的更新是以異步的方式進(jìn)行。UI 線程首先會(huì)執(zhí)行用戶代碼,然后如果這些代碼使得 UI 界面的數(shù)據(jù)發(fā)生的改變,UI 線程將對(duì)其 UI 界面進(jìn)行更新。這意味著,并不是每執(zhí)行一條更改 UI 數(shù)據(jù)的代碼,它都會(huì)在 UI 界面上馬上生效。有時(shí)候,這會(huì)導(dǎo)致一些問題。
??UI 線程是單線程的,指的 UI 界面是只通過一個(gè)線程來完成它界面的更新,指的不是凡是涉及 UI 的程序只能使用一個(gè)線程。UI 應(yīng)用相比于后臺(tái)應(yīng)用,只是多了幾個(gè)與處理 UI 相關(guān)的線程而已,沒什么額外的線程個(gè)數(shù)限制。
??如果想了解更多關(guān)于同步、異步、阻塞的知識(shí),可見筆者的另一篇博客:同步阻塞、同步非阻塞、異步阻塞、異步非阻塞:https://blog.csdn.net/wangpaiblog/article/details/117236684
JavaFX 中使用多線程
??為防止 UI 界面被阻塞,又因?yàn)?UI 線程是單線程的,因此應(yīng)該選擇在其它線程執(zhí)行非常耗時(shí)的操作。可以選擇當(dāng)需要執(zhí)行非常耗時(shí)的操作時(shí),新開一個(gè)線程,將此非常耗時(shí)的操作放到新開一個(gè)線程去執(zhí)行。
??在 JavaFX 中使用多線程一般使用兩個(gè)類:ExecutorService、Task<Integer>。Task<Integer> 有一個(gè)方法 call,可以在這個(gè)方法去執(zhí)行耗時(shí)操作。具體代碼如下:
// 假設(shè)方法 someJavafxFun 位于 JavaFX 的某個(gè)組件的定義中 public void someJavafxFun() {ExecutorService executor = Executors.newCachedThreadPool();Task<Integer> task = new Task<>() {@Overrideprotected Integer call() {// TODO 執(zhí)行耗時(shí)操作return null; // 如果需要結(jié)果反饋,可以在此處提供反饋值}};/*** 如果不需要結(jié)果反饋,也可以直接使用 executor.execute(task);* * 可以使用 result.get() 來獲取上面的反饋值。但這個(gè)方法是同步阻塞的*/var result = executor.submit(task);/*** 方法 getWindow() 獲得的其實(shí)是 Stage。此段代碼是用于在應(yīng)用關(guān)閉時(shí)回收資源。* * 對(duì)于真正的程序,方法 setOnCloseRequest 要設(shè)置在 Stage 被創(chuàng)建處。* 因?yàn)榉椒?setOnCloseRequest 會(huì)覆蓋其它 setOnCloseRequest 的效果,所以此方法只能執(zhí)行一次。* 為了達(dá)到這個(gè)效果,需要將 task 與 executor 設(shè)置成全局的,或者將其封裝在一個(gè)全局靜態(tài)方法中*/this.getScene().getWindow().setOnCloseRequest(event -> {if (task != null) {task.cancel();}if (executor != null) {executor.shutdown();}Platform.exit();}); }??Task<T> 是 JavaFX 的一個(gè)類,它繼承至 FutureTask<T>。而 FutureTask<T>、ExecutorService 均為原生的 Java 多線程中的類,后續(xù)的操作均可依照 Java 多線程理論中的流程來完成。
【附】
??可以使用 Thread.currentThread().getName() 來查看某代碼位于的線程。如下。其中,JavaFX 的 UI 界面所在的線程名為 JavaFX Application Thread。
System.out.println("【編號(hào)xxx】 執(zhí)行本代碼 XXX 的線程是:" + Thread.currentThread().getName());JavaFX 中保證 UI 線程安全
??JavaFX 中的 UI 和大多數(shù)其它的編程語言中的一樣,不是線程安全的,因?yàn)樗菃尉€程的。在單線程中無需考慮線程安全的問題,但在多線程中需要考慮。介于本文討論的重點(diǎn),這里不打算解釋什么是線程安全。那么,如果在 JavaFX 中使用了多線程,如何保證 UI 線程安全呢?
??在 JavaFX 中,可以在 UI 之外的線程中,使用方法 Platform.runLater 來執(zhí)行與 UI 直接相關(guān)的操作。如下:
Platform.runLater(() -> {/* // TODO 更新 UI 數(shù)據(jù)的代碼 */});??注意:為保證 UI 界面的流暢,只需將與 UI 直接相關(guān)的代碼置入上述的方法 Platform.runLater 中,不要在此方法中放多余的代碼,否則就失去了使用多線程的意義。
總結(jié)與補(bǔ)充
-
為保證 UI 界面的流暢,需要將某些代碼放入新建線程中,這些代碼需要同時(shí)滿足以下條件:
-
非常耗時(shí)或執(zhí)行時(shí)間不能保證最壞結(jié)果也符合要求
-
與操作 UI 數(shù)據(jù)不直接相關(guān)
-
并非與 UI 數(shù)據(jù)強(qiáng)同步。
例如:如果 UI 需要請(qǐng)求一個(gè)資源,如果該資源不能獲得,UI 就會(huì)崩潰,那么獲取該資源的代碼不能放在新建線程中,除非可以保證此線程與 UI 線程可以同步。因?yàn)?#xff0c;在不使用任何機(jī)制的情況下,新建的線程都是非阻塞的,如果選擇將獲取該資源的代碼放在新建線程中,在這種情況下,UI 中請(qǐng)求資源的方法會(huì)立即返回,這個(gè)時(shí)候獲取到的是這個(gè)資源的初始值(一般是 null)。也就是說,如果選擇將獲取該資源的代碼放在新建線程中,相當(dāng)于直接注釋掉了新建線程獲取資源這部分的代碼。
-
-
為保證 UI 的線程安全,在其它線程不能直接更改 UI 的數(shù)據(jù),必須將更改 UI 數(shù)據(jù)的代碼傳于方法 Platform.runLater 中運(yùn)行。
總結(jié)
以上是生活随笔為你收集整理的JavaFX 中使用多线程与保证 UI 线程安全的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Maven 配置文件 POM 的常用插件
- 下一篇: 优化 UI 应用启动时间的方法