Java桌面技術(shù)

          Java Desktop Technology

          常用鏈接

          統(tǒng)計(jì)

          友情連接

          最新評(píng)論

          《FilthyRichClients》讀書筆記(一)-SwingのEDT

              《FilthyRichClients》讀完了前幾個(gè)章節(jié),現(xiàn)將我的體會(huì)結(jié)合工作以來(lái)從事Swing桌面開發(fā)的經(jīng)驗(yàn),對(duì)本書的一些重要概念進(jìn)行一次分析,對(duì)書中的一些遺漏與模糊的地方及時(shí)補(bǔ)充,同時(shí)使讀者消除長(zhǎng)期以來(lái)“Swing性能低、界面丑陋”諸如此類的舊觀念。讀書筆記僅談?wù)勎覍?duì)Swing的理解,難免會(huì)犯錯(cuò)誤,還望廣大讀者指教。

              書中第二章-Swing渲染基本原理 中對(duì)Swing的線程做了系統(tǒng)地介紹。相比其他同類Swing教程,已經(jīng)講得非常深入了。但是如果讀者之前對(duì)線程的掌握程度有限,尤其是編寫代碼比較隨意的coder們,動(dòng)輒就大量編寫類似下面這樣的代碼:
          jButton1.addActionListener(new ActionListener(){
             public void actionPerformed(ActionEvent e) {
              // TODO
             }
            });
          這樣的代碼可能是netBeans這樣的工具生成的“杰作”。但是如果這個(gè)人再懶惰一點(diǎn),可能會(huì)直接在TODO下面寫上長(zhǎng)長(zhǎng)一堆代碼,還伴隨著不可預(yù)知的I/O操作,很多人指責(zé)界面被僵住是Swing性能的問(wèn)題。在新式的JDK中,Swing已經(jīng)在性能方面改進(jìn)了很多,完全可以這么說(shuō):與應(yīng)用程序自身的業(yè)務(wù)計(jì)算相比,界面上的耗時(shí)可以忽略。但是如果上述惡習(xí)改不掉的話,Swing永遠(yuǎn)“快”不起來(lái),SWT也同樣如此,因?yàn)樗鼈兌际菃尉€程圖形工具包。
              書上有這樣一段話:“EventQueue的派發(fā)機(jī)制由單獨(dú)的一個(gè)線程管理,這個(gè)線程稱為事件派發(fā)線程(EDT)”。和其他很多桌面API一樣,Swing將GUI請(qǐng)求放入一個(gè)事件隊(duì)列中執(zhí)行。如果不明白什么是事件隊(duì)列、EDT,它們是如何運(yùn)作的,那么首先必須澄清四個(gè)重要的概念:分別是同步與異步、串行與并行、生產(chǎn)者消費(fèi)者模式、事件隊(duì)列。(不同領(lǐng)域串行與并行的含義可能是不同的)
              同步與異步:同步是程序在發(fā)起請(qǐng)求后開始處理事件并等待處理的結(jié)果或等待請(qǐng)求執(zhí)行完畢,在此之前程序被block住直到請(qǐng)求完成。而異步是當(dāng)前程序發(fā)起請(qǐng)求后立即返回,當(dāng)前程序不會(huì)立即處理該事件并等待處理的結(jié)果,請(qǐng)求是在稍后的某一時(shí)間才被處理。
              串行與并行:所謂串行是指多個(gè)要處理請(qǐng)求順序執(zhí)行,處理完一個(gè)再處理下一個(gè);并行可以理解為并發(fā),是同時(shí)處理多個(gè)請(qǐng)求(實(shí)際上我們只能理解為是這樣,特別是CPU數(shù)目少于線程數(shù)的機(jī)器而言,真正意義的并發(fā)是不存在的,各個(gè)線程只是斷斷續(xù)續(xù)地交替地執(zhí)行)。下圖演示了串行與并行的機(jī)制。可以這么說(shuō),在引入多線程之前,對(duì)于同一進(jìn)程或者程序而言執(zhí)行的都是串行操作。

          串行:  

          并行:

              生產(chǎn)者/消費(fèi)者模式:可以想象這樣一副場(chǎng)景,某車間的一條傳送帶,有一個(gè)或多個(gè)入口不斷產(chǎn)生待加工的貨物,這種不斷產(chǎn)生貨物的稱為生產(chǎn)者;傳送帶的末端是一個(gè)或多個(gè)工人在加工貨物,稱作消費(fèi)者。有時(shí)由于傳送帶上沒(méi)有足夠的貨物使得某一工人暫時(shí)空閑,有時(shí)又由于部分貨物需加工的時(shí)間較長(zhǎng)出現(xiàn)傳送帶上待加工的貨物堆積。
                                                                  
          如果用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的生產(chǎn)者消費(fèi)者模型,利用線程的等待/通知機(jī)制很容易實(shí)現(xiàn)。給出最基本的同步隊(duì)列的參考實(shí)現(xiàn)

          public class SyncQueue<T> {
           private List<T> queue;

           private final Object LOCK = new Object();

           public SyncQueue() {
            queue = new LinkedList<T>();
           }

           public T pop() throws InterruptedException {
            synchronized (LOCK) {
             while (queue.isEmpty()) {
              try {
               LOCK.wait();
              } catch (InterruptedException ex) {
               throw ex;
              }
             }
             T e = queue.remove(0);
             return e;
            }
           }

           public void push(T e) {
            synchronized (LOCK) {
             queue.add(e);
             LOCK.notifyAll();
            }
           }
          }
          在JDK 5中新出現(xiàn)了許多具有并發(fā)性的數(shù)據(jù)結(jié)構(gòu)在java.util.concurrent包中,它們適合于特殊的場(chǎng)合,本帖不作解釋。

              事件隊(duì)列:在計(jì)算機(jī)數(shù)據(jù)結(jié)構(gòu)中,隊(duì)列是一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu)。其一、它是線性的;其二、元素是先進(jìn)先出的,也就是說(shuō)進(jìn)入隊(duì)列的元素必須從末端進(jìn)入,先入隊(duì)的元素先得到執(zhí)行,后入隊(duì)的元素等待前面的元素執(zhí)行完畢出隊(duì)后才能執(zhí)行,隊(duì)列的處理方式是執(zhí)行完一個(gè)再執(zhí)行下一個(gè)。隊(duì)列與線程安全是兩個(gè)不同的概念,如果要將隊(duì)列加上線程安全的特性,只需要仿照上述生產(chǎn)者/消費(fèi)者加上線程的等待/通知即可。

          而Swing的事件隊(duì)列就類似(基本原理相似,但是Swing內(nèi)部實(shí)現(xiàn)會(huì)做些優(yōu)化)于上述的事件隊(duì)列,說(shuō)它是單線程圖形工具包指的是僅有單一消費(fèi)者,也就是常說(shuō)的事件分發(fā)線程(EDT),一般來(lái)講,除非你的應(yīng)用程序停止,否則EDT會(huì)永不間斷地徘徊在處理請(qǐng)求與等待請(qǐng)求之間。下圖是Swing事件隊(duì)列的實(shí)現(xiàn)機(jī)制:



          很顯然,如果在加工某一個(gè)貨物上花費(fèi)很長(zhǎng)的時(shí)間,那么后續(xù)的貨物只好等待。對(duì)于單一線程的事件隊(duì)列來(lái)說(shuō)有兩個(gè)非常突出的特性:一、將同步操作轉(zhuǎn)為異步操作。二、將并行處理轉(zhuǎn)換為串行順序處理

          如果你能理解上述圖,那么你就應(yīng)該意識(shí)到:EDT要處理所有GUI操作,它是職責(zé)分明且非常忙碌的。也就是說(shuō)你要記住兩條原則:一、職責(zé)分明,任何GUI請(qǐng)求都應(yīng)該在EDT中調(diào)用。二、需要處理的GUI請(qǐng)求非常多,包括窗口移動(dòng)、組件自動(dòng)重繪、刷新,它很忙,所以任何與GUI無(wú)關(guān)的處理不要由EDT來(lái)負(fù)責(zé),尤其是I/O這種耗時(shí)的操作。
              書中還講到Swing不是一個(gè)“安全線程”的API,為什么要這樣設(shè)計(jì),再回看上圖就會(huì)明白:Swing的線程安全不是靠自身組件的API來(lái)保障,雖然repaint方法是這樣,但是大多數(shù)Swing API是非線程安全的,也就是說(shuō)不能在任意地方調(diào)用,它應(yīng)該只在EDT中調(diào)用。Swing的線程安全靠事件隊(duì)列和EDT來(lái)保障。
              invokeLater和invokeAndWait:前文提到,Swing自身不是線程安全,對(duì)非EDT的并發(fā)調(diào)用需通過(guò)invokeLater(runnable)和invokeAndWait(runnable)使請(qǐng)求插入到隊(duì)列中等待EDT去執(zhí)行。invokeLater(runnable)方法是異步的,它會(huì)立即返回,具體何時(shí)執(zhí)行請(qǐng)求并不確定,所以命名invokeLater是稍后調(diào)用。invokeAndWait(runnable)方法是同步的,它被調(diào)用結(jié)束會(huì)立即block當(dāng)前線程(調(diào)用invokeAndWait的那個(gè)線程)直到EDT處理完那個(gè)請(qǐng)求。invokeAndWait一般的應(yīng)用是取得Swing組件的數(shù)據(jù),例如取得JSlider組件的當(dāng)前值:
          public class Task implements Runnable {
           private JSlider slider;
           private int value;
           public Task() {
            //slider = ...;
           }
           @Override
           public void run() {
            try {
             Thread.sleep(1000); // 有意停住1秒
            } catch (InterruptedException e) {
            }
            value = slider.getValue();
           }
           public int getValue() {
            return value;
           }
          }
          而外部非EDT線程可以這樣調(diào)用:
          Task task = new Task();
            try {
             EventQueue.invokeAndWait(task);
            } catch (InterruptedException e) {
            } catch (InvocationTargetException e) {
            }
            int value = task.getValue();
          當(dāng)線程運(yùn)行到EventQueue.invokeAndWait(task)時(shí)會(huì)立即被block至少1秒,待invokeAndWait返回時(shí)已經(jīng)可以安全地取到值了。invokeAndWait被這樣命名也反映了使用的意圖:調(diào)用并等待結(jié)果。invokeAndWait有非常重要的一條準(zhǔn)則是它不能在EDT中被調(diào)用,否則程序會(huì)拋出Error,請(qǐng)求也不會(huì)去執(zhí)行。

          public static void invokeAndWait(Runnable runnable)
                       throws InterruptedException, InvocationTargetException {

                  if (EventQueue.isDispatchThread()) {
                      throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
                  }

           class AWTInvocationLock {} // 聲明這個(gè)類只是鎖的標(biāo)志,沒(méi)有其他意義
                  Object lock = new AWTInvocationLock();

                  InvocationEvent event =
                      new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock,
              true);

                  synchronized (lock) {
                      Toolkit.getEventQueue().postEvent(event); //添加進(jìn)事件隊(duì)列
                      lock.wait(); // block當(dāng)前線程
                  }

                  Throwable eventThrowable = event.getThrowable();
                  if (eventThrowable != null) {
                      throw new InvocationTargetException(eventThrowable);
                  }
              }


          為什么要有這樣一條限制?結(jié)合前文不難得出-防止死鎖。如果invokeAndWait在EDT中調(diào)用,那么首先將請(qǐng)求壓進(jìn)隊(duì)列,然后EDT便被block(因?yàn)樗褪钦{(diào)用invokeAndWait的當(dāng)前線程)等待請(qǐng)求結(jié)束通知它繼續(xù)運(yùn)行,而實(shí)際上請(qǐng)求將永遠(yuǎn)得不到執(zhí)行,因?yàn)樗诘却?duì)列的調(diào)度使EDT執(zhí)行它,這就陷入一個(gè)僵局-EDT等待請(qǐng)求先執(zhí)行,請(qǐng)求又等待EDT對(duì)隊(duì)列的調(diào)度。彼此等待對(duì)方釋放鎖是造成死鎖的四類條件之一。Swing有意地避免了這類情況的發(fā)生。

              書中也提到了同步的繪制請(qǐng)求,作為隊(duì)列,一條基本原則就是先進(jìn)先出。那么paintImmediately到底是怎樣的呢?顯然這個(gè)調(diào)用請(qǐng)求不會(huì)稍后去執(zhí)行,也就是說(shuō)不會(huì)插入到隊(duì)列的末尾等到排在它前面的請(qǐng)求執(zhí)行完再去執(zhí)行它,而是“破壞”順序性原則優(yōu)先去執(zhí)行,前面提到,Swing的事件隊(duì)列相對(duì)基礎(chǔ)的同步隊(duì)列做了很多優(yōu)化,那么這么說(shuō)它是否被插入到隊(duì)列最前面呢,也就是0這個(gè)位置?貌似也不是,書上說(shuō)“已經(jīng)在EDT中調(diào)用的方法中間...”,那么就是比當(dāng)前正在處理的繪制請(qǐng)求還要優(yōu)先,因?yàn)樗钱?dāng)前繪制請(qǐng)求的一部分,所以當(dāng)前繪制請(qǐng)求(EDT正在處理的那個(gè)請(qǐng)求)要等它處理完成后再繼續(xù)處理。(好好體會(huì)吧)
              SwingWorker:推薦一篇Blog,http://blog.sina.com.cn/s/blog_4b6047bc010007so.html,作者是原Sun中國(guó)工程研究院的陳維雷先生,他對(duì)Swing的造詣非淺,他的Blog中有3篇介紹這一主題的文章,詳盡程度要比該書詳細(xì)得多。

              最后,談一下理解EDT對(duì)設(shè)計(jì)模式的幫助。通過(guò)上述對(duì)事件隊(duì)列和EDT的分析,有這樣一種體會(huì):事件隊(duì)列是一個(gè)非常好的處理并發(fā)設(shè)計(jì)模型,不僅Swing用它來(lái)處理后臺(tái),Java的很多地方都在用,只不過(guò)對(duì)于處理服務(wù)器端的并發(fā)請(qǐng)求有多個(gè)處理線程在等候處理請(qǐng)求,也就是常說(shuō)的線程池。而對(duì)于單用戶的桌面應(yīng)用,單線程調(diào)用要比多現(xiàn)成API更簡(jiǎn)單,“Swing后臺(tái)這樣做是為了保證事件的順序可預(yù)見(jiàn)性”,而且相對(duì)于服務(wù)器,客戶端桌面層的請(qǐng)求要少得多,所以單線程就足夠應(yīng)對(duì)了。
          單一Thread化的訪問(wèn)

          通過(guò)EDT,使得不具備線程安全的Swing函數(shù)庫(kù)避開了并發(fā)訪問(wèn)的問(wèn)題。如果你也有一個(gè)不具備thread安全性的函數(shù)庫(kù)并想在multithreaded環(huán)境下使用應(yīng)該怎么辦?只要你是從單一的thread來(lái)訪問(wèn)這個(gè)函數(shù)庫(kù),程序就不會(huì)遭遇到任何數(shù)據(jù)同步的問(wèn)題。

          posted on 2008-06-23 22:49 sun_java_studio@yahoo.com.cn(電玩) 閱讀(10873) 評(píng)論(7)  編輯  收藏 所屬分類: NetBeans

          評(píng)論

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT 2008-06-24 08:09 日月雨林@gmail.com

          很有啟發(fā)性的一篇文章! 謝謝!  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT 2008-06-24 13:35 zht

          FilthyRichClients對(duì)于java2d學(xué)習(xí) 是本好書  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT 2008-06-25 09:41 Matthew Chen

          好久沒(méi)看lz的blog了。
          講到paintImmediately和“已經(jīng)在EDT中調(diào)用的方法中間...”,就好像edt嚴(yán)格的先進(jìn)后出提供的一個(gè)小變通,可以超越之前請(qǐng)求但未繪制事件先執(zhí)行。
          我想到swt繪制的一個(gè)問(wèn)題,如隨拖拽行為而產(chǎn)生的多個(gè)界面上的重繪,我們要作處理使其同時(shí)發(fā)生而沒(méi)有滯后,方法是在其中一個(gè)的paintcontrol中加入對(duì)另一個(gè)的redraw調(diào)用,后來(lái)看來(lái)api,不知是否是update這個(gè)方法能夠解決的問(wèn)題。  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT[未登錄](méi) 2008-07-06 13:42 William Chen

          Hi樓主,
          好久不聯(lián)系了。我最近為Eclipse做了一個(gè)Swing的界面設(shè)計(jì)工具插件,模仿netbeans的,我已經(jīng)把它開源了,如果你感興趣的話,希望你能加入進(jìn)來(lái)一起開發(fā)。你看看這兒我寫的介紹:
          http://www.javaeye.com/topic/208787
          -William Chen  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT 2008-07-10 13:05 sun_java_studio@yahoo.com.cn(電玩)

          今天在Javaeye上發(fā)現(xiàn)了一個(gè)Blog。
          http://blog.palantirtech.com/category/swing/

          值得借鑒  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT 2008-07-15 17:31 ALLENME

          真是牛人聚集的地方啊。
          敢問(wèn)一下:Willim chen現(xiàn)在還寫blog么?  回復(fù)  更多評(píng)論   

          # re: 《FilthyRichClients》讀書筆記(一)-SwingのEDT[未登錄](méi) 2008-12-12 11:45 Matthew Chen

          再閱,果然溫故知新,看透了不少。  回復(fù)  更多評(píng)論   

          TWaver中文社區(qū)
          主站蜘蛛池模板: 和田市| 福鼎市| 黎城县| 漳州市| 高碑店市| 嫩江县| 吉木乃县| 屏山县| 平和县| 梓潼县| 崇州市| 渭源县| 英吉沙县| 珲春市| 泰兴市| 昆山市| 灵丘县| 长治县| 丰台区| 海晏县| 大悟县| 西乌珠穆沁旗| 越西县| 黄浦区| 东光县| 建水县| 仙游县| 台北市| 利辛县| 楚雄市| 岱山县| 德保县| 永济市| 汝南县| 子洲县| SHOW| 大安市| 浏阳市| 乡城县| 资源县| 宣汉县|