熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java開發中的線程安全選擇與Swing

2022-06-13   來源: Java核心技術 

  Swing API的設計目標是強大靈活和易用特別地我們希望能讓程序員們方便地建立新的Swing組件不論是從頭開始還是通過擴展我們所提供的一些組件
  
    出於這個目的我們不要求Swing組件支持多線程訪問相反我們向組件發送請求並在單一線程中執行請求
  
    本文討論線程和Swing組件目的不僅是為了幫助你以線程安全的方式使用Swing API而且解釋了我們為什麼會選擇現在這樣的線程方案
  
    本文包括以下內容
     單線程規則Swing線程在同一時刻僅能被一個線程所訪問一般來說這個線程是事件派發線程(eventdispatching thread)
  
     規則的例外有些操作保證是線程安全的
  
     事件分發如果你需要從事件處理(eventhandling)或繪制代碼以外的地方訪問UI那麼你可以使用SwingUtilities類的invokeLater()或invokeAndWait()方法
  
     創建線程如果你需要創建一個線程——比如用來處理一些耗費大量計算能力或受I/O能力限制的工作——你可以使用一個線程工具類如SwingWorker或Timer
  
     為什麼我們這樣實現Swing我們將用一些關於Swing的線程安全的背景資料來結束這篇文章
  
    Swing的規則是
  
    一旦Swing組件被具現化(realized)所有可能影響或依賴於組件狀態的代碼都應該在事件派發線程中執行
  
    這個規則可能聽起來有點嚇人但對許多簡單的程序來說你用不著為線程問題操心在我們深入如何撰寫Swing代碼之前讓我們先來定義兩個術語具現化(realized)和事件派發線程(eventdispatching thread)
  
    具現化的意思是組建的paint()方法已經或可能會被調用一個作為頂級窗口的Swing組件當調用以下方法時將被具現化setVisible(true)show()或(可能令你驚奇)pack()當一個窗口被具現化它包含的所有組件都被具現化另一個具現化一個組件的方法是將它放入到一個已經具現化的容器中稍後你會看到一些對組件具現化的例子
  
    事件派發線程是執行繪制和事件處理的線程例如paint()和actionPerformed()方法會自動在事件派發線程中執行另一個將代碼放到事件派發線程中執行的方法是使用SwingUtilities類的invokeLater()方法
  
    所有可能影響一個已具現化的Swing組件的代碼都必須在事件派發線程中執行但這個規則有一些例外
  
    有些方法是線程安全的在Swing API的文檔中線程安全的方法用以下文字標記
  
    This method is thread safe although most Swing methods are not
    (這個方法是線程安全的盡管大多數Swing方法都不是
  
  一個應用程序的GUI常常可以在主線程中構建和顯示下面的典型代碼是安全的只要沒有(Swing或其他)組件被具現化
  
  public class MyApplication
  {
   public static void main(String[] args)
   {
    JFrame f = new JFrame(Labels); // 在這裡將各組件
     // 加入到主框架……
     fpack();
     fshow();
     // 不要再做任何GUI工作……
    }
  }
  
    上面所示的代碼全部在main線程中運行對fpack()的調用使得JFrame以下的組件都被具現化這意味著fshow()調用是不安全的且應該在事件派發線程中執行盡管如此只要程序還沒有一個看得到的GUIJFrame或它的裡面的組件就幾乎不可能在fshow()返回前收到一個paint()調用因為在fshow()調用之後不再有任何GUI代碼於是所有GUI工作都從主線程轉到了事件派發線程因此前面所討論的代碼實際上是線程安全的
  
    一個applet的GUI可以在init()方法中構造和顯示現有的浏覽器都不會在一個applet的init()和start()方法被調用前繪制它因而在一個applet的init()方法中構造GUI是安全的只要你不對applet中的對象調用show()或setVisible(true)方法
  
    要順便一提的是如果applet中使用了Swing組件就必須實現為JApplet的子類並且組件應該添加到的JApplet內容窗格(content pane)中而不要直接添加到JApplet對任何applet你都不應該在init()或start()方法中執行費時的初始化操作而應該啟動一個線程來執行費時的任務
  
    下述JComponent方法是安全的可以從任何線程調用repaint()revalidate()和invalidate()repaint()和revalidate()方法為事件派發線程對請求排隊並分別調用paint()和validate()方法invalidate()方法只在需要確認時標記一個組件和它的所有直接祖先
  
    監聽者列表可以由任何線程修改調用addListenerTypeListener()和removeListenerTypeListener()方法總是安全的對監聽者列表的添加/刪除操作不會對進行中的事件派發有任何影響
  
    注意revalidate()和舊的validate()方法之間的重要區別是revalidate()會緩存請求並組合成一次validate()調用這和repaint()緩存並組合繪制請求類似
  
    大多數初始化後的GUI工作自然地發生在事件派發線程一旦GUI成為可見大多數程序都是由事件驅動的如按鈕動作或鼠標點擊這些總是在事件派發線程中處理的
  
    不過總有些程序需要在GUI成為可見後執行一些非事件驅動的GUI工作比如
  
    在成為可用前需要進行長時間初始化操作的程序這類程序通常應該在初始化期間就顯示出GUI然後更新或改變GUI初始化過程不應該在事件派發線程中進行否則重繪組件和事件派發會停止盡管如此在初始化之後GUI的更新/改變還是應該在事件派發線程中進行理由是線程安全
  
    必須響應非AWT事件來更新GUI的程序例如想象一個服務器程序從可能運行在其他機器上的程序得到請求這些請求可能在任何時刻到達並且會引起在一些可能未知的線程中對服務器的方法調用這個方法調用怎樣更新GUI呢?在事件派發線程中執行GUI更新代碼
  
    SwingUtilities類提供了兩個方法來幫助你在事件派發線程中執行代碼
  
     invokeLater()要求在事件派發線程中執行某些代碼這個方法會立即返回不會等待代碼執行完畢
  
     invokeAndWait()行為與invokeLater()類似除了這個方法會等待代碼執行完畢一般地你可以用invokeLater()來代替這個方法
  
    下面是一些使用這幾個API的例子請同時參閱《The Java Tutorial》中的BINGO example尤其是以下幾個類CardWindowControlPanePlayer和OverallStatusPane
  
  
  使用invokeLater()方法
  
    你可以從任何線程調用invokeLater()方法以請求事件派發線程運行特定代碼你必須把要運行的代碼放到一個Runnable對象的run()方法中並將此Runnable對象設為invokeLater()的參數invokeLater()方法會立即返回不等待事件派發線程執行指定代碼這是一個使用invokeLater()方法的例子
  
  Runnable doWorkRunnable = new Runnable()
  {
   public void run()
   {
    doWork();
    }
  };
  SwingUtilitiesinvokeLater(doWorkRunnable);
  
    使用invokeAndWait()方法
  
    invokeAndWait()方法和invokeLater()方法很相似除了invokeAndWait()方法會等事件派發線程執行了指定代碼才返回在可能的情況下你應該盡量用invokeLater()來代替invokeAndWait()如果你真的要使用invokeAndWait()請確保調用invokeAndWait()的線程不會在調用期間持有任何其他線程可能需要的鎖
  這是一個使用invokeAndWait()的例子
  
  void showHelloThereDialog() throws Exception
  {
   Runnable showModalDialog = new Runnable()
   {
    public void run()
    {
     JOptionPaneshowMessageDialog( myMainFrame Hello There);
     }
    };
   SwingUtilitiesinvokeAndWait (showModalDialog);
  }
  
    類似地假設一個線程需要對GUI的狀態進行存取比如文本域的內容它的代碼可能類似這樣
  
  void printTextField()
    throws Exception {
     final String[] myStrings = new String[];
     Runnable getTextFieldText = new Runnable() {
      public void run() {
       myStrings[] = textFieldgetText();
       myStrings[] = textFieldgetText();
      }
     };
     SwingUtilitiesinvokeAndWait (getTextFieldText);
     Systemoutprintln(myStrings[] + + myStrings[]);}
  
    如果你能避免使用線程最好這樣做線程可能難於使用並使得程序的debug更困難一般來說對於嚴格意義下的GUI工作線程是不必要的比如對組件屬性的更新
  
    不管怎麼說有時候線程是必要的下列情況是使用線程的一些典型情況
  
    執行一項費時的任務而不必將事件派發線程鎖定例子包括執行大量計算的情況會導致大量類被裝載的情況(如初始化)和為網絡或磁盤I/O而阻塞的情況
  
    重復地執行一項操作通常在兩次操作間間隔一個預定的時間周期
  
    要等待來自客戶的消息
  
    你可以使
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26726.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.