程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 深入淺出Java多線程(2)-Swing中的EDT(事件分發線程)

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

編輯:關於JAVA

本文主要解決的問題是:

如何使其Swing程序只能運行一個實例?

拋開Swing,我們的程序是通過java 命令行啟動一個進程來執行的,該問題 也就是說要保證這個進程的唯一性,當然如果能夠訪問系統的接口,得到進程的 信息來判斷是否已有進程正在運行,不就解決了嗎?但是如何訪問系統的接口呢 ?如何要保證在不同的平台上都是OK的呢?我的思路是用文件鎖,當然我相信肯 定有更好的方法,呵呵,希望讀者能夠指出。

文件鎖是JDK1.4 NIO提出的,可以在讀取一個文件時,獲得文件鎖,這個鎖 應該是系統維護的,JVM應該是調用的系統文件鎖機制,例子如下:

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();
  }

當代碼獲得鎖後:我們試圖編輯這個文件是就會:

如果在啟動一個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();。返回的結束是 ture , 也就是得不到文件的鎖。

這就是對於進程唯一性問題我的解決思路,通過鎖定文件使其再啟動時得不 到鎖文件而無法啟動。

說到這裡,跟今天Swing中的EDT好像還沒有關系,對於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線程就退出了,上面獲得文件鎖,並持有鎖的邏輯往哪 裡寫呢? 有人會說事件分發線程EDT,真的嗎?

由於我沒有做過Swing的項目,僅僅做過個人用的財務管理小軟件,還沒有深 入理解過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.

加上去以後怎麼界面沒有任何反應了呢?

代碼如下:

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(0, 1));
   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 線程執行,看右下角調用關系,EventDispathThread 啟動 Run方法,然後pumpEvents 取 事件,然後從EventQueue取到InvocationEvent 執行Dispath

Dispath調用的就是我們在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中的呢?當然是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有個認識了吧。

1. EDT 只有一個線程,雖然getFileLock是實現Runnable接口,它調用的時 候不是star方法啟動新線程,而是直接調用run方法。

2. invokeAndWait將你寫的getFileLock塞到EventQueue中。

3. Swing 事件機制采用Product Consumer模式 EDT不斷的取EventQueue中的 事件執行(消費者)。其他線程可以將事件塞入EventQueue中,比如鼠標點擊 Button是,將注冊在BUttion的事件塞入EventQueue中。

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

所以絕對不能將持有鎖的邏輯塞到EventQueue,而應該放到外邊main線程或 者其他線程裡面。

提到invokeAndWait,還必須說說invokelater 這兩個區別在哪裡呢?

invokeAndWait與invokelater區別: 看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:當在main方法中調用SwingUtils.invokelater,後,把事件塞入 EventQueue就返回了,main線程不會阻塞。

invokeAndWait: 當在Main方法中調用SwingUtils.invokeAndWait 後,看代 碼片段:

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

main線程獲得lock 後就wait()了,直到事件分發線程調用 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(0, 1));
   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);

當然這只是個解決方案,如何友好給給用戶提示以及鎖定那個文件就要根據 具體情況而定了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved