新的起點 新的開始

          快樂生活 !

          深入淺出Java多線程(2)-Swing中的EDT(事件分發(fā)線程)

          接深入淺出Java多線程系列(1),本文主要解決的問題是:
          如何使其Swing程序只能運行一個實例?
          拋開Swing, 我們的程序是通過java 命令行啟動一個進程來執(zhí)行的,該問題也就是說要保證這個進程的唯一性,當(dāng)然如果能夠訪問系統(tǒng)的接口,得到進程的信息來判斷是否已有進程正在運行,不就解決了嗎?但是如何訪問系統(tǒng)的接口呢?如何要保證在不同的平臺上都是OK的呢?我的思路是用文件鎖,當(dāng)然我相信肯定有更好的方法,呵呵,希望讀者能夠指出。
          文件鎖是JDK1.4 NIO提出的,可以在讀取一個文件時,獲得文件鎖,這個鎖應(yīng)該是系統(tǒng)維護的,JVM應(yīng)該是調(diào)用的系統(tǒng)文件鎖機制,例子如下:
          import java.io.FileNotFoundException;
          import java.io.IOException;
          import java.io.RandomAccessFile;
          import java.nio.channels.FileChannel;
          import java.nio.channels.FileLock;
          /**
           *
           * 
          @author vma
           
          */
          public class temp1 {
            
          public static void main(String args[]) throws FileNotFoundException, InterruptedException, IOException{
              RandomAccessFile r 
          = new RandomAccessFile("d://testData.java","rw");
              FileChannel temp 
          = r.getChannel();
              FileLock fl 
          = temp.lock();
              System.out.println(fl.isValid());
              Thread.sleep(
          100000);
              temp.close();
            }
          當(dāng)代碼獲得鎖后:我們試圖編輯這個文件是就會:


          如果在啟動一個Java Main方法時:
          public class temp2 {
            
          public static void main(String args[]) throws FileNotFoundException, InterruptedException, IOException{
              RandomAccessFile r 
          = new RandomAccessFile("d://testData.java","rw");
              FileChannel temp 
          = r.getChannel();
              FileLock fl 
          = temp.tryLock();
              System.out.println(fl
          == null);
              temp.close();。
          返回的結(jié)束是 ture , 也就是得不到文件的鎖。

          這就是對于進程唯一性問題我的解決思路,通過鎖定文件使其再啟動時得不到鎖文件而無法啟動。
          說到這里,跟今天Swing中的EDT好像還沒有關(guān)系,對于Swing程序,Main方法中一般像這樣:
            public static void main(String[] args) {
              
          try {
                UIManager.setLookAndFeel(UIManager
                    .getCrossPlatformLookAndFeelClassName());
              } 
          catch (Exception e) {
              }

              
          //Create the top-level container and add contents to it.
              JFrame frame = new JFrame("SwingApplication");
              SwingApplication app 
          = new SwingApplication();
              Component contents 
          = app.createComponents();
              frame.getContentPane().add(contents, BorderLayout.CENTER);

              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.pack();
              frame.setVisible(
          true);
              啟動Jframe后,Main線程就退出了,上面獲得文件鎖,并持有鎖的邏輯往哪里寫呢? 有人會說事件分發(fā)線程EDT,真的嗎?
              由于我沒有做過Swing的項目,僅僅做過個人用的財務(wù)管理小軟件,還沒有深入理解過EDT,不管怎么說先把那段邏輯加到EDT,
              怎么加呢 用SwingUtilities
          static void invokeAndWait(Runnable doRun)
                    Causes doRun.run() to be executed synchronously on the AWT event dispatching thread.
          static void invokeLater(Runnable doRun)
                    Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread.
              加上去以后怎么界面沒有任何反應(yīng)了呢?
          代碼如下:
          package desktopapplication1;
          import java.awt.BorderLayout;
          import java.awt.Component;
          import java.awt.GridLayout;
          import java.awt.event.ActionEvent;
          import java.awt.event.ActionListener;
          import java.awt.event.KeyEvent;

          import java.io.FileNotFoundException;
          import java.io.IOException;
          import java.io.RandomAccessFile;
          import java.lang.reflect.InvocationTargetException;
          import java.nio.channels.FileChannel;
          import java.nio.channels.FileLock;
          import java.util.logging.Level;
          import java.util.logging.Logger;
          import javax.swing.BorderFactory;
          import javax.swing.JButton;
          import javax.swing.JFrame;
          import javax.swing.JLabel;
          import javax.swing.JPanel;
          import javax.swing.SwingUtilities;
          import javax.swing.UIManager;

          public class SwingApplication {
            
          private static String labelPrefix = "Number of button clicks: ";

            
          private int numClicks = 0;

            
          public Component createComponents() {
              
          final JLabel label = new JLabel(labelPrefix + "0    ");

              JButton button 
          = new JButton("I'm a Swing button!");
              button.setMnemonic(KeyEvent.VK_I);
              button.addActionListener(
          new ActionListener() {
                
          public void actionPerformed(ActionEvent e) {
                  numClicks
          ++;
                  label.setText(labelPrefix 
          + numClicks);
                }
              });
              label.setLabelFor(button);

              
          /*
               * An easy way to put space between a top-level container and its
               * contents is to put the contents in a JPanel that has an "empty"
               * border.
               
          */
              JPanel pane 
          = new JPanel();
              pane.setBorder(BorderFactory.createEmptyBorder(
          30//top
                  30//left
                  10//bottom
                  30//right
                  );
              pane.setLayout(
          new GridLayout(01));
              pane.add(button);
              pane.add(label);

              
          return pane;
            }

            
          public static void main(String[] args) throws InterruptedException {
              
          try {
                UIManager.setLookAndFeel(UIManager
                    .getCrossPlatformLookAndFeelClassName());
              } 
          catch (Exception e) {
              }

              
          //Create the top-level container and add contents to it.
              JFrame frame = new JFrame("SwingApplication");
              SwingApplication app 
          = new SwingApplication();
              Component contents 
          = app.createComponents();
              frame.getContentPane().add(contents, BorderLayout.CENTER);

              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.pack();
              frame.setVisible(
          true);
                  
          try {
                      SwingUtilities.invokeAndWait(
          new getFileLock());
                  } 
          catch (InvocationTargetException ex) {
                    ex.printStackTrace();
                  }
            }
            
          }
          class getFileLock implements Runnable{

              
          public void run() {
                  
          try {
                      RandomAccessFile r 
          = null;
                   
          try {
                          r 
          = new RandomAccessFile("d://testData.java""rw");
                      } 
          catch (FileNotFoundException ex) {
                        ex.printStackTrace();
                      }
                      FileChannel temp 
          = r.getChannel();
                      FileLock fl 
          = null;
                      
          try {
                          fl 
          = temp.lock();
                      } 
          catch (IOException ex) {
                          Logger.getLogger(getFileLock.
          class.getName()).log(Level.SEVERE, null, ex);
                      }
              
                      System.out.println(fl.isValid());
                      
          try {
                          Thread.sleep(Integer.MAX_VALUE);
                      } 
          catch (InterruptedException ex) {
                         ex.printStackTrace();
                      }
                      temp.close();
                  } 
          catch (IOException ex) {
                     ex.printStackTrace();
                  }
              }
          }
          打個斷點看看怎么了,斷點就在這里     Thread.sleep(Integer.MAX_VALUE); 看看那個線程暫停了 看圖片:



          看到了吧,我們寫的那個getFileLock 是由AWT-EventQueue-0  線程執(zhí)行,看右下角調(diào)用關(guān)系, EventDispathThread 啟動 Run方法, 然后pumpEvents 取事件,然后從EventQueue取到InvocationEvent 執(zhí)行Dispath
          Dispath調(diào)用的就是我們在getFileLock寫的run() 方法, JDK代碼如下:
            public void dispatch() {
              
          if (catchExceptions) {
                  
          try {
                  runnable.run();
                  } 
                  
          catch (Throwable t) {
                          
          if (t instanceof Exception) {
                              exception 
          = (Exception) t;
                          }
                          throwable 
          = t;
                  }
              }
              
          else {
                  runnable.run();
              }

              
          if (notifier != null) {
                  
          synchronized (notifier) {
                  notifier.notifyAll();
                  }
              }
              }
            runnable.run();
          而如何將我們寫的getFileLock加入的那個EventQueue中的呢?當(dāng)然是SwingUtilities.invokeAndWait(new getFileLock());
          看JDK代碼:
           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 {}
                  Object lock 
          = new AWTInvocationLock();

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

                  
          synchronized (lock) {
                     
          Toolkit.getEventQueue().postEvent(event);
                      lock.wait();
                  }
          Toolkit.getEventQueue().postEvent(event);把我們寫的getFileLock 塞進了EventQueue.
          這下讀者對EDT有個認(rèn)識了吧。
          1. EDT 只有一個線程, 雖然getFileLock是實現(xiàn)Runnable接口,它調(diào)用的時候不是star方法啟動新線程,而是直接調(diào)用run方法。
          2.
          invokeAndWait將你寫的getFileLock塞到EventQueue中。
          3.
          Swing 事件機制采用Product Consumer模式 EDT不斷的取EventQueue中的事件執(zhí)行(消費者)。其他線程可以將事件塞入EventQueue中,比如鼠標(biāo)點擊Button是,將注冊在BUttion的事件塞入EventQueue中
          。

          所以我們將
          getFileLock作為事件插入進去后 EDT分發(fā)是調(diào)用Thread.sleep(Integer.MAX_VALUE)就睡覺了,無暇管塞入EventQueue的其他事件了,比如關(guān)閉窗體。

          所以絕對不能將持有鎖的邏輯塞到EventQueue,而應(yīng)該放到外邊main線程或者其他線程里面。
          提到invokeAndWait,還必須說說invokelater 這兩個區(qū)別在哪里呢?
          invokeAndWait與invokelater區(qū)別: 看JDK代碼:

           public static void invokeLater(Runnable runnable) {
                  Toolkit.getEventQueue().postEvent(
                      
          new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
              }

           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 {}
                  Object lock 
          = new AWTInvocationLock();

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

                  
          synchronized (lock) {
                      Toolkit.getEventQueue().postEvent(event);
                      lock.wait();
                  }

                  Throwable eventThrowable 
          = event.getThrowable();
                  
          if (eventThrowable != null) {
                      
          throw new InvocationTargetException(eventThrowable);
                  }
              }
          invokelater:當(dāng)在main方法中調(diào)用SwingUtils.invokelater,后,把事件塞入EventQueue就返回了,main線程不會阻塞。
          invokeAndWait: 當(dāng)在Main方法中調(diào)用SwingUtils.invokeAndWait 后,看代碼片段:
                  synchronized (lock) {
                      Toolkit.getEventQueue().postEvent(event);
                      lock.wait();
                  }

          main線程獲得lock 后就wait()了,直到事件分發(fā)線程調(diào)用lock對象的notify喚醒main線程,否則main 就干等著吧。

          這下明白了吧!
          總之,對于我們問題最簡單的方法就是是main線程里,或者在其他線程里處理。
          最后的解決方案是:
          package desktopapplication1;
          import java.awt.BorderLayout;
          import java.awt.Component;
          import java.awt.GridLayout;
          import java.awt.event.ActionEvent;
          import java.awt.event.ActionListener;
          import java.awt.event.KeyEvent;

          import java.io.FileNotFoundException;
          import java.io.IOException;
          import java.io.RandomAccessFile;
          import java.nio.channels.FileChannel;
          import java.nio.channels.FileLock;

          import javax.swing.BorderFactory;
          import javax.swing.JButton;
          import javax.swing.JFrame;
          import javax.swing.JLabel;
          import javax.swing.JPanel;
          import javax.swing.UIManager;

          public class SwingApplication {
            
          private static String labelPrefix = "Number of button clicks: ";

            
          private int numClicks = 0;

            
          public Component createComponents() {
              
          final JLabel label = new JLabel(labelPrefix + "0    ");

              JButton button 
          = new JButton("I'm a Swing button!");
              button.setMnemonic(KeyEvent.VK_I);
              button.addActionListener(
          new ActionListener() {
                
          public void actionPerformed(ActionEvent e) {
                  numClicks
          ++;
                  label.setText(labelPrefix 
          + numClicks);
                }
              });
              label.setLabelFor(button);

              
          /*
               * An easy way to put space between a top-level container and its
               * contents is to put the contents in a JPanel that has an "empty"
               * border.
               
          */
              JPanel pane 
          = new JPanel();
              pane.setBorder(BorderFactory.createEmptyBorder(
          30//top
                  30//left
                  10//bottom
                  30//right
                  );
              pane.setLayout(
          new GridLayout(01));
              pane.add(button);
              pane.add(label);

              
          return pane;
            }

            
          public static void main(String[] args) throws InterruptedException {
              
          try {
                UIManager.setLookAndFeel(UIManager
                    .getCrossPlatformLookAndFeelClassName());
              } 
          catch (Exception e) {
              }
              Thread t = new Thread(new getFileLock()); 
              t.setDaemon(
          true);
              t.start();

              
          //Create the top-level container and add contents to it.
              JFrame frame = new JFrame("SwingApplication");
              SwingApplication app 
          = new SwingApplication();
              Component contents 
          = app.createComponents();
              frame.getContentPane().add(contents, BorderLayout.CENTER);

              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.pack();
              frame.setVisible(
          true);

            }
            
          }
          class getFileLock implements Runnable{
           

              
          public void run() {
                  
          try {
                      RandomAccessFile r 
          = null;
                   
          try {
                          r 
          = new RandomAccessFile("d://testData.java""rw");
                      } 
          catch (FileNotFoundException ex) {
                        ex.printStackTrace();
                      }
                      FileChannel temp 
          = r.getChannel();
                   
                      
          try {
            
                        FileLock fl = temp.tryLock();
                        
          if(fl == null) System.exit(1);

                        
                      } 
          catch (IOException ex) {
                     ex.printStackTrace();
                      }
                      
          try {
                          Thread.sleep(Integer.MAX_VALUE);
                      } 
          catch (InterruptedException ex) {
                         ex.printStackTrace();
                      }
                      temp.close();
                  } 
          catch (IOException ex) {
                     ex.printStackTrace();
                  }
              }
          }
          在Main方法里啟動一個Daemon線程,持有鎖,如果拿不到鎖,就退出 if(fl == null) System.exit(1);
          當(dāng)然這只是個解決方案,如何友好給給用戶提示以及鎖定那個文件就要根據(jù)具體情況而定了。

          posted on 2008-08-24 02:32 advincenting 閱讀(4630) 評論(4)  編輯  收藏

          評論

          # re: 深入淺出Java多線程(2)-Swing中的EDT(事件分發(fā)線程) 2008-08-25 23:02 Matthew Chen

          恩,EDT是這樣的,invokeXXX就有點像SWT里面的Display.synXXX,具體名字記不得了,trylock比lock好,是馬上返回而非阻塞吧。  回復(fù)  更多評論   

          # re: 深入淺出Java多線程(2)-Swing中的EDT(事件分發(fā)線程) 2008-09-01 10:05 w

          1."所以絕對不能將持有鎖的邏輯塞到EventQueue,而應(yīng)該放到外邊main線程或者其他線程里面。"
          (應(yīng)該是Integer.MAX_VALUE時間內(nèi)sleep)的邏輯不能放到EDT里吧?在程序的整個運行期間內(nèi)都持有鎖是沒問題的
          獲得鎖之前沒有阻塞住后續(xù)代碼的執(zhí)行,也不合適。如果鎖獲取不成功,后續(xù)代碼的執(zhí)行是沒有意義的,我覺得還得用invokeAndWait
          2.是什么意思?Integer.MAX_VALUE毫秒后釋放鎖(2147483647/(1000*60*60*24)≈24.9天)
          假定第一個程序運行24.9(這個時間對程序運行來說并不長)天之后,第二個就可以順利啟動了,這就違背了單實例的本意了
          其他:用socket機制實現(xiàn)單實例運行也是一個不錯的方法====================================================
          不知道我理解的對不對,錯誤指出請指正  回復(fù)  更多評論   

          # re: 深入淺出Java多線程(2)-Swing中的EDT(事件分發(fā)線程) 2008-09-02 21:09 advincenting

          1. 看來你還是沒有理解 invokeAndWait含義,那會阻塞調(diào)用該方法的線程,對于例子就是Main.
          2. 呵呵 如果 Integer.MAX_VALUE不夠 可以用Long.MAX_VALUE啊. 當(dāng)然Socket也可以 但跟你環(huán)境有很大關(guān)系,況且無緣無故啟動一個Socket 端口,占用資源不說,就殺毒軟件都把你滅了。原理上當(dāng)然可以!  回復(fù)  更多評論   

          # re: 深入淺出Java多線程(2)-Swing中的EDT(事件分發(fā)線程) 2008-09-03 22:01 w

          @advincenting
          用invokeAndWait獲得鎖,在執(zhí)行后續(xù)的程序,有什么問題?  回復(fù)  更多評論   


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           

          公告

          Locations of visitors to this pageBlogJava
        1. 首頁
        2. 新隨筆
        3. 聯(lián)系
        4. 聚合
        5. 管理
        6. <2008年8月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          統(tǒng)計

          常用鏈接

          留言簿(13)

          隨筆分類(71)

          隨筆檔案(179)

          文章檔案(13)

          新聞分類

          IT人的英語學(xué)習(xí)網(wǎng)站

          JAVA站點

          優(yōu)秀個人博客鏈接

          官網(wǎng)學(xué)習(xí)站點

          生活工作站點

          最新隨筆

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 东乌珠穆沁旗| 武陟县| 广昌县| 阜阳市| 兴海县| 平泉县| 敖汉旗| 克什克腾旗| 潞西市| 南木林县| 新平| 上思县| 耒阳市| 徐闻县| 高邮市| 孟连| 隆回县| 迁西县| 仁怀市| 巴彦县| 班玛县| 辽宁省| 广汉市| 长春市| 镇雄县| 天津市| 莱芜市| 文昌市| 玛纳斯县| 桦川县| 周口市| 佛山市| 宜川县| 岳普湖县| 衢州市| 双辽市| 寿阳县| 天津市| 三原县| 赞皇县| 万州区|