本文主要解决的问题是:
如何使其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);
当然这只是个解决方案,如何友好给给用户提示以及锁定那个文件就要根据具体情况而定了。