新的起点 新的开始

快乐生活 !

深入浅出Java多线程(2)-Swing中的EDT(事件分发线程)

接深入浅出Java多线程系列(1),本文主要解决的问题是:
如何使其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(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  线程执行,看右下角调用关系, 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(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);
当然这只是个解决方案,如何友好给给用户提示以及锁定那个文件就要根据具体情况而定了。

posted on 2008-08-24 02:32 advincenting 阅读(4614) 评论(4)  编辑  收藏

评论

# re: 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 2008-08-25 23:02 Matthew Chen

恩,EDT是这样的,invokeXXX就有点像SWT里面的Display.synXXX,具体名字记不得了,trylock比lock好,是马上返回而非阻塞吧。  回复  更多评论   

# re: 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 2008-09-01 10:05 w

1."所以绝对不能将持有锁的逻辑塞到EventQueue,而应该放到外边main线程或者其他线程里面。"
(应该是Integer.MAX_VALUE时间内sleep)的逻辑不能放到EDT里吧?在程序的整个运行期间内都持有锁是没问题的
获得锁之前没有阻塞住后续代码的执行,也不合适。如果锁获取不成功,后续代码的执行是没有意义的,我觉得还得用invokeAndWait
2.是什么意思?Integer.MAX_VALUE毫秒后释放锁(2147483647/(1000*60*60*24)≈24.9天)
假定第一个程序运行24.9(这个时间对程序运行来说并不长)天之后,第二个就可以顺利启动了,这就违背了单实例的本意了
其他:用socket机制实现单实例运行也是一个不错的方法====================================================
不知道我理解的对不对,错误指出请指正  回复  更多评论   

# re: 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 2008-09-02 21:09 advincenting

1. 看来你还是没有理解 invokeAndWait含义,那会阻塞调用该方法的线程,对于例子就是Main.
2. 呵呵 如果 Integer.MAX_VALUE不够 可以用Long.MAX_VALUE啊. 当然Socket也可以 但跟你环境有很大关系,况且无缘无故启动一个Socket 端口,占用资源不说,就杀毒软件都把你灭了。原理上当然可以!  回复  更多评论   

# re: 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 2008-09-03 22:01 w

@advincenting
用invokeAndWait获得锁,在执行后续的程序,有什么问题?  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

公告

Locations of visitors to this page

导航

<2008年8月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

常用链接

留言簿(13)

随笔分类(71)

随笔档案(179)

文章档案(13)

新闻分类

IT人的英语学习网站

JAVA站点

优秀个人博客链接

官网学习站点

生活工作站点

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜