Swing與線程

swing與線程

使用線程是爲了提高程序的響應速度,當程序需要做某些很耗時的任務時,不應阻塞用戶接口而應啓動另一個工作器線程。但是我們必須小心工作器線程所做的事情。
Swing不是線程安全的,不要嘗試在多個線程中操作用戶界面元素,否則程序可能崩潰。
Swing爲什麼不設計成線程安全的:首先同步要耗費時間(Swing的速度本來就令人不滿了);使用線程安全包的用戶界面程序員不能很好的使其同步,容易產生死鎖的構件。

每一個Java應用程序都開始於一個主線程中的main方法。在Swing程序中main方法處理:
1.首先調用構造器在框架窗口中排列構件;
2.然後調用框架窗口的setVisible方法。

當顯式第一個窗口時,第二個線程(事件分發線程)被創建。所有事件的通知:如調用actionPerformed方法或paintComponent方法,都在事件派發線程中執行。而主線程會保持運行直到main方法運行結束(一般來說main方法在窗口顯式不久就退出了)。

其他線程像:向事件隊列發佈事件的線程,都在後臺運行,只是這些線程對應用程序員都是不可見的。所有代碼運行在事件派發線程中。
當你將線程和Swing一起使用時應遵守下列規則:
1.如果一個動作佔用的時間很長,就啓動一個新的線程來執行他。因爲如果事件派發線程執行的任務佔用了大量的時間,那麼用戶界面幾乎不能及時響應任何事件了。
2.如果一個動作在輸入或輸出上阻塞了,就啓動一個新線程來處理輸入輸出。不要因爲網絡連接或其他IO處理無法作出響應而無限期的凍結用戶界面。
3.如果需要等待指定的時間,不要讓事件派發線程睡眠,而應該使用定時器,只能在事件指派線程上訪問 Swing 組件。
4.在線程中做的事情不能接觸用戶界面。在啓動線程前,應該先閱讀來自用戶界面的信息然後再啓動他們,一旦這些線程完成就從事件派發線程中更新用戶界面。(此又稱爲Swing程序的單一線程規則不過也有一些列外:
1. 只有很少的Swing方法是線程安全的:JTextComponent.setText JTextArea.insert JTextArea.append JTextArea.replaceRange。
2. 還有JComponent類中的repaint方法和revalidate方法可以從任意線程中調用。repaint方法調度一個重繪事件。如果在構件的內容發生變化時,構件的大小和位置也都必須進行相應的更新,那麼就應該使用revalidate方法。ravalidate方法將構件佈局標記爲無效,並調度一個佈局事件(像paint事件,佈局事件也是聚集的。如果事件隊列中存在多個佈局事件,佈局只被重新計算一次)。
3. repaint使用較多但revalidate方法並不常用:revalidate主要用來在內容改變後強制執行一次構件的佈局。傳統的AWT也有一個validate方法強制執行一次構件的佈局。對於swing構件,應該調用revalidate方法。要注意的是JFrame是一個Component而不是JComponent,因此要強制執行一次JFrame的佈局應該調用validate方法。
4. 你可以在任意一個線程裏安全的添加和移除一個事件監聽器。(當然事件監聽器的方法會在事件派發線程中被出發)。
5. 你可以構建構件,設定它們的屬性,然後把它們添加到容器中,只要這些構件還沒有被realized。若構件能夠接收paint或validation事件了,那麼就表示這個構件已經被實現了。只要在這個構件上調用了setVisible(true)或pack方法,或者構件被添加到一個已經實現的容器中,就可以滿足這個條件。一旦構件realized就不能再次從另一個線程操縱它了。我們可以在main方法中在調用setVisible(true)之前創建一個應用程序的GUI,也可以在applet的構造器或init方法中創建GUI。

考慮下面的情況:假設觸發一個單獨的線程運行一項耗時的任務。你想通過GUI界面來表現該線程任務的進展情況,任務完成時你想再次更新GUI。但你不能從你的線程中接觸到Swing構件。如:如果你想更新進度條或標籤上的內容,你不能僅在你的線程中設置它的值。
爲了解決此問題,在任何線程中你都可以使用兩種方便有效的方法來向事件隊列中添加任意的動作。例如:你想在一個線程中週期性的更新標籤來表明進度,你不能從你的線程中調用label.setText。而應該使用EventQueue類的invokeLater和invokeAndWait方法使所調用的方法在事件派發線程中執行。

應該將Swing代碼放入實現了Runnable接口的類的run方法中,然後創建一個該類的對象並將其傳入靜態的invokeLater或invokeAndWait方法。
如:
EventQueue.invokeLater(new Runnable(){
public void run(){
label.setText(percentage+”% Complete”);
}
});
當事件發佈到事件隊列中時,invokeLater方法立即返回,而run方法則被異步執行。invokeAndWait方法等待直到潤方法確實被執行過爲止。
處理更新進度標籤的情況中,invokeLater方法更爲適用。因爲用戶更希望工作器線程更快的完成工作而不是得到十分精確的進度指示器。
上述兩個方法都在事件派發線程中執行而沒有任何新的線程被創建。

static void invokeLater:。在等待處理的線程被處理後,使Runnable對象的run方法在事件派發線程中執行
static void invokeAndWait:在等待處理的線程被處理後,使Runnable對象的run方法在事件派發線程中執行,該調用會阻塞直到run方法終止。

Swing工作器:
當用戶發佈一條很費時的任務時,可以通過啓動一個新線程來完成工作。就像開始介紹的線程應該使用EventQueue.invokeLater方法來更新用戶界面。
SwingWorker類可以很輕鬆的完成這種工作。

工作器線程的典型UI行爲:
1.在工作開始之前完成UI的初始化。
2.在每個工作單元之後更新UI來顯示進度。
3.整個工作完成之後,對UI作出最後的更新。

Swing中的併發:
併發的小心使用對Swing編程人員是非常重要的。好的Swing程序能有效利用併發而不會導致程序被凍結–不管做什麼不管何時程序總能及時響應用戶接口。因此程序員要掌握Swing框架是如何使用線程的。
主要包括以下三種類型的線程的使用:
1.初始線程:用來執行程序的初始化代碼。
2.事件派遣線程:執行所有的事件處理代碼;大部分與Swing框架交互的代碼也由該線程來執行。
3.工作者線程:也稱爲後臺線程。用來執行耗時的後臺任務。
程序員不必專門寫代碼來明確創建這些線程:因爲它們是由運行時或Swing框架自動提供的。程序員的任務是利用好這些線程創建響應及時的可維護的Swing程序。
就像其他運行在Java平臺的程序一樣,Swing程序也可以創建線程和線程池。
javax.swing.SwingWorker是一個非常重要的類,他可以實現worker thread的任務和其他線程任務之間的通信並進行調節。

1.初始線程:每一個程序都有一個線程集它們是應用程序在邏輯上開始執行的地方。在一般的標準程序中:僅有一種這樣的線程:調用主類中的main方法。對於Applet這些初始線程執行applet對象的構造以及調用該對象的init方法和start方法。這些初始化動作可能發生在單個線程也可能2個或3個不同的線程,這取決於Java平臺的實現。

在Swing程序中,初始線程並不需做很多的事情,它們最主要的任務是創建一個Runnable對象來初始化GUI以及調度所創建的對象到event dispath thread上執行。一旦GUI被創建,程序主要由GUI事件驅動執行,GUI事件會使短小的處理代碼由event dispath thread來執行。應用程序代碼能調度額外的任務去event dispath 線程來執行(不過這些任務要能很快執行完,因此也不能影響事件的處理interface with event processing),也可以調度到worker thread上執行(對於那些需長時間運行的任務)。

初始線程通過javax.swing.SwingUtilities.invokeLater(僅僅調度該任務就立即返回)或javax.swing.SwingUtilities.invokeAndWait(調度該任務直到任務執行完畢才返回) 這兩個方法(此兩方法都有以一個Runnable對象(用來定義任務的對象)作爲參數)來進行GUI的創建。
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
}
在applet中,GUI的創建任務必須由init方法中調用invokeAndWait來完成。否則可能出現在GUI還沒創建好init方法就已經返回,導致瀏覽器在加載該applet時出現問題。然而在其他程序中對GUI的創建通常是初始線程最後才做的事情,因此使用invokeLater和invokeAndWait都是一樣的。
初始線程爲什麼不自己簡單的就創建GUI呢:那是因爲用來創建Swing組件和與Swing組件進行交互的代碼大部分都運行在event dispath thread之中。

事件派遣線程:Swing事件處理代碼運行在event dispath thread上。大部分調用Swing方法的代碼也是運行在該線程上。由於大部分Swing方法都不是線程安全的因此運行於同一線程上這是有必要的。如果從很多其他線程調用這些線程不安全的方法導致線程間相互干擾或內存不一致的錯誤。那些線程安全的Swing組件方法可以安全的被任何線程調用。而所有其他線程不安全的方法只能被事件分發線程調用。若忽略這個規則,可能出現功能在大部分情況下是正確的但有時會遭遇出乎意料的錯誤,這些錯誤很難重現。
也許你會很奇怪爲什麼Java平臺如此重要的一部分不設計成線程安全的。主要是因爲任何試圖創建線程安全的GUI庫都將面臨嚴重的問題。

我們應該時刻謹記運行在event dispath thread上的任務需要滿足短小不太耗時、能快速運行完的條件。其他大型任務可以用invokeLater或invokeAndWait方法在應用程序中調度。如果你想判斷你的代碼是不是運行在event dispath thread上,你可以調用javax.swing.SwingUtilities.isEventDispatchThread。

工作者線程和SwingWorker:
當一個Swing程序要執行很耗時的任務時,它通常需要使用worker threads也就是所說的background threads(後臺線程)。每一個運行在worker thread上的任務都由javax.swing.SwingWorker的實例來表示。SwingWorker是一個抽象類。因此你必須定義一個繼承自SwingWorker的子類來創建對象,當然匿名內部類也是一種創建形式。

SwingWorker提供了很多通信和控制的特真:
1.SwingWorker的子類能定義done方法:當後臺線程完成時被event dispath thread自動的調用。
2.SwingWorker實現了java.util.concurrent.Future。該接口允許background task返回一個值給其他線程,接口中有方法取消background task以及發現是否有background task完成或被取消。
3.background task通過調用SwingWorker.publish來促使SwingWorker.process被event dispath thread調用以返回中間結果。
4.background task能定義捆綁屬性。對這些屬性的改變都會觸發相應的事件,從而導致事件處理方法被event dispath thread所調用。
javax.swing.SwingWorker 是在JDK6.0加進來的。在此之前也有一個叫SwingWorker的類並廣泛用於同一個目的。
老的SwingWorker不是Java平臺規範的一部分,也沒有作爲JDK的一部分提供。
javax.swing.SwingWorker 完全是一個新類。在功能上它並不是嚴格上的老的SwingWorker的功能超集新老SwingWorker類中具有相同功能的方法的名字並不一定相同。而且舊的SwingWordker是可以複用的。javax.swing.SwingWorker 的實例對新的background task是必須的。

簡單Background Tasks:
下面是一個簡單的但是潛在很耗時的任務:TumbleItem applet加載一系列圖像文件用於動畫製作。如果圖像文件由初始線程來加載,將可能在GUI顯示以前出現長時間的延遲(因爲GUI的初始化和顯示由初始線程來完成)。如果由event dispath thread加載,GUI可能出現短暫的無法及時響應事件的情形。爲了避免上述的情況,TumleItem從他的初始線程創建並執行SwingWorker的一個實例對象。該對象的doInBackground方法,在一個worker thread中執行將圖像加載到一個ImageIcon數組並返回該數組的引用。然後done方法在一個event dispath thread中執行並調用get方法以獲取該圖像數組引用將該引用賦值給applet class的imgs成員。這樣就可以讓TumbleItem能快速的構造GUI而不用等待裝載圖像的完成。
SwingWorker worker = new SwingWorker

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章