Java多線程開發系列之番外篇:事件派發線程---EventDispatchThread


事件派發線程是java Swing開發中重要的知識點,在安卓app開發中,也是非常重要的一點。今天我們在多線程開發中,穿插進來這個線程。分別從線程的來由、原理和使用方法三個方面來學習事件派發線程。

 

一、事件派發線程的前世今生

事件(Event)派發(Dispatch)線程(Thread)簡寫為EDT,也就是各個首字母的簡寫。在一些書或者博客里邊也將其譯為事件分發線程、事件調度線程。巴拉巴拉,總之,知道這些名字就行。筆者認為這里翻譯成派發更准確點。

熟悉Swing和awt編程的小伙伴對事件派發線程應該都不陌生。如果你提反對意見的話,只能說明你對Swing和awt編程還不夠熟悉。

事件派發線程誕生的故事背景是這樣的:

界面上各個控件對象都有保存自己的數據變量。如果出現多線程操作就會出現很多問題,諸如數據變臟,數組越界,空引用等等問題。

舉個栗(例)子

 

 

線程A發現panel中還有數據要顯示(check data),於是調用滾動條向下滾動。這時,panel內部要調用數據中為展示的數據用來顯示。可是在展示的過程中,線程發生了切換。由其它線程B刪掉了需要展示的數據,這時線程A再次被喚醒繼續運行,顯示接下來的內容。由於已經過了Check Data的邏輯。所以接下來就要調用已經不存在的數據用來展示。最后就會出現各種奇怪的問題。(如果你沒看懂,就理解成各個線程最終都在操作控件的數據源,則控件在顯示的時候就可能會出現異常)。

通常來說解決這種多線程冰法問題方式就是"鎖"或者"同步"。

當時Sun公司的Swing小組最初也是這個思路,但是讓Swing小組最終改變主意的由於接下來的兩個原因:

1、數據同步在保證線程安全的同時,很耗費時間。UI最重要的就是界面響應速度,畢竟誰也不想面對一個幻燈片在操作。

2、Swing小組調查了其他小組在線程安全的用戶界面工具包方(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )面的經驗后,發現結果並不是那么的美好:開發線程安全包的工程師被各種同步操作搞暈了頭,程序經常發生死鎖。

就此,Swing小組決定使用單一線程來控制整個界面的控件繪制。這個線程就是事件派發線程。

事件派發線程就是這樣被創造出來的:

 

二、事件派發線程的原理

事件派發線程的原理其實非常的簡單,在界面后台始終只有這一個線程在工作,這個線程就是事件派發線程。當你有需要操作界面的行為時,將這些行為添加到事件派發線程的事件隊列中,事件派發線程會依次執行這個隊列中的請求。

這有點像單核cpu進行多線程操作的場景,不同的地方是,這時候事件派發線程的作用是單核cpu。

具體內容可以查看下圖(圖片來源於網絡)

 

各個線程將GUI請求排成隊列,然后由事件派發線程依次執行這個隊列中的請求。

如果從設計模式的角度來看,這個地方是一個典型的"消費者"模式,有興趣的小伙伴可以查閱下相關的設計模式內容,這里就不展開贅述了。

了解了事件派發線程原理之后,我們會發現這樣一個問題:

eventQueue中的事件沒有輕重緩急之分,是遵循FIFO的原則的。那么如果前邊的請求非常耗時,需要大量的db請求、IO等操作,那么后邊的請求只能一直等待。

當初舍棄'同步'是為了快,現在界面還是會卡死,違背了初衷。

基於以上,Swing開發人員提出了兩點在使用事件派發線程時需要遵守的原則:

1、只有事件派發線程可以調用Swing組件,其他線程都離組件遠遠的。(有些地方稱這條准則為單一線程規則single-thread rule)

2、如果某一個GUI請求非常耗時,就不要把這個請求發送給事件派發線程。直到這個請求通過其他線程處理之后,最后的少部分界面請求再發送給事件派發線程。

 

三、怎么使用事件派發線程

上面說了非常多,但是不知道怎么使用事件派發線程,則上邊所述也就沒有什么用了。

首先,前文中提到了事件派發線程是啟動GUI后,(其實這里還存在有一個初始化線程,短暫的啟動GUI的生命過程)系統自動啟動的一個線程。

所以我們就不用手動創建和運行線程了。我們要做的就(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )是向事件派發線程中添加各種GUI請求到eventQUEUE中去即可。

 

Swing為我們提供了三個常用的API

1 SwingUtilities.invokeAndWait(Runnable runnable)//同步請求,發送請求的線程會一直等到EDT執行完畢自己的請求后,才會繼續執行剩余代碼;
2 
3 SwingUtilities.invokeLater(Runnable runnable)//異步請求,發送請求的線程在請求添加到EDT的eventQUEUE后,才會執行剩余代碼;
4 
5 SwingUtilities.isEventDispatchThread()//判斷當前線程是否為事件派發線程。

一般來說在編寫請求代碼的時候,最好先判斷下執行線程是否為事件派發線程,然后在選擇是直接執行還是添加到事件隊列中。

值得注意的是這里會存在一個問題:

就是如果當前線程就是事件派發線程時,是不允許其執行invokeAndWait()同步方法的。

這是由於如果出現這種情況EDT就會停頓(wait)在這個點,等待EDT去執行添加的請求,同時由於EDT已經停頓在了這個點,那么EDT也就不會去處理eventQUEUE中的請求,形成了一種死鎖。

好在JDK中已經對這種情況做了校驗,所以上面沒太看懂的同學無需太在意,只要記住結果即可:

 

 

最后我們再來一個實際工作中代碼的例子

 1 if(SwingUtilities.isEventDispatchThread())  2 {  3  OptTree.RefreshNode();  4 }  5 else
 6 {  7     SwingUtilities.invokeAndWait(new Runnable()  8  {  9  @Override 10         public void run() 11  { 12  OptTree.RefreshNode(); 13  } 14  }); 15 }


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM