java swing里面大部分类都不是线程安全的,如果通过多个线程去操作swing对象,很可能会出现很多诡异的现象,如果你想让它变成线程安全的,就需要用一个特殊的线程去操作swing对象,也就是EDT线程,也就是事件调度线程(Event Dispatch Thread,EDT)
一个GUI 程序有很多线程同时运行,其中有一个叫做 事件调度线程(EDT),用来处理我们在程序里面所有的回调(最常见的就是我们点击按钮后执行的actionPerformed方法),所有的操作Swing对象的操作必须放在这个线程里面,否则就会出问题
看一个例子
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
/**
* <code>NotInEDTSample</code> just demonstrates the usage of Swing EDT simply.
*
* @author Jimmy.haung(SZ Team)
* @since <i>DUI (Mar 25, 2013)</i>
*/
public class NotInEDTSample extends JFrame {
private static final long serialVersionUID = 1L;
private JTextField m_txt;
public NotInEDTSample() {
initGUI();
notInEDT();
}
/**
* Init the GUI
*/
public void initGUI() {
this.setTitle("a simple EDT Sample");
m_txt = new JTextField();
getContentPane().add(m_txt, BorderLayout.CENTER);
}
/**
* Process not under the EDT. 这里我启动了10个线程来改变<code>m_txt</code>的内容.
*/
private void notInEDT() {
for (int i = 0; i < 4; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
m_txt.setText("我不在EDT中操作!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
/*private void notInEDT() {
for (int i = 0; i < 4; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
m_txt.setText("我在EDT中操作!");
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}*/
/**
* Launch the application.
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
NotInEDTSample oFrame = new NotInEDTSample();
oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oFrame.setLocationRelativeTo(null);
oFrame.setSize(300, 200);
oFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
我们在notInEDT用其他线程去改变Text的值,应该很快就会出问题,而且没有抛出异常
只要我们把下面的这个样子,让EDT线程去处理就没有问题了
private void notInEDT() {
for (int i = 0; i < 4; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
m_txt.setText("我在EDT中操作!");
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
这里调用了invokeLater方法把UI操作丢给EDT线程去处理,看看说明
让一个Runnable接口的run方法能够异步在EDT线程里面执行,等于把这个操作假如到一个队列队尾,等前面的操作都完成了再执行这个操作
/**
* Causes <i>doRun.run()</i> to be executed asynchronously on the
* AWT event dispatching thread. This will happen after all
* pending AWT events have been processed. This method should
* be used when an application thread needs to update the GUI.
* In the following example the <code>invokeLater</code> call queues
* the <code>Runnable</code> object <code>doHelloWorld</code>
* on the event dispatching thread and
* then prints a message.
* <pre>
* Runnable doHelloWorld = new Runnable() {
* public void run() {
* System.out.println("Hello World on " + Thread.currentThread());
* }
* };
*
* SwingUtilities.invokeLater(doHelloWorld);
* System.out.println("This might well be displayed before the other message.");
* </pre>
* If invokeLater is called from the event dispatching thread --
* for example, from a JButton's ActionListener -- the <i>doRun.run()</i> will
* still be deferred until all pending events have been processed.
* Note that if the <i>doRun.run()</i> throws an uncaught exception
* the event dispatching thread will unwind (not the current thread).
* <p>
* Additional documentation and examples for this method can be
* found in
* <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How to Use Threads</a>,
* in <em>The Java Tutorial</em>.
* <p>
* As of 1.3 this method is just a cover for <code>java.awt.EventQueue.invokeLater()</code>.
* <p>
* Unlike the rest of Swing, this method can be invoked from any thread.
*
* @see #invokeAndWait
*/
public static void invokeLater(Runnable doRun)
有一个可以功能类似但是有区别的方法,invokeAndWait方法,功能和invokeLater差不多,也是可以把run方法放到EDT里面执行,但是区别在于是同步的,并且不能在EDT里面被调用
/**
* Causes <code>doRun.run()</code> to be executed synchronously on the
* AWT event dispatching thread. This call blocks until
* all pending AWT events have been processed and (then)
* <code>doRun.run()</code> returns. This method should
* be used when an application thread needs to update the GUI.
* It shouldn't be called from the event dispatching thread.
* Here's an example that creates a new application thread
* that uses <code>invokeAndWait</code> to print a string from the event
* dispatching thread and then, when that's finished, print
* a string from the application thread.
* <pre>
* final Runnable doHelloWorld = new Runnable() {
* public void run() {
* System.out.println("Hello World on " + Thread.currentThread());
* }
* };
*
* Thread appThread = new Thread() {
* public void run() {
* try {
* SwingUtilities.invokeAndWait(doHelloWorld);
* }
* catch (Exception e) {
* e.printStackTrace();
* }
* System.out.println("Finished on " + Thread.currentThread());
* }
* };
* appThread.start();
* </pre>
* Note that if the <code>Runnable.run</code> method throws an
* uncaught exception
* (on the event dispatching thread) it's caught and rethrown, as
* an <code>InvocationTargetException</code>, on the caller's thread.
* <p>
* Additional documentation and examples for this method can be
* found in
* <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How to Use Threads</a>,
* in <em>The Java Tutorial</em>.
* <p>
* As of 1.3 this method is just a cover for
* <code>java.awt.EventQueue.invokeAndWait()</code>.
*
* @exception InterruptedException if we're interrupted while waiting for
* the event dispatching thread to finish excecuting
* <code>doRun.run()</code>
* @exception InvocationTargetException if an exception is thrown
* while running <code>doRun</code>
*
* @see #invokeLater
*/
public static void invokeAndWait(final Runnable doRun)
throws InterruptedException, InvocationTargetException
下面我来系统分析一下为什么不能在EDT里面调用 invokeAndWait
首先需要理解java swing的event 队列,我们对UI的基本所有操作都会生成一个event,加入到event队列里面,由EDT线程来逐一处理,比如我们鼠标点击一个按钮,就是把注册在按钮里面的事件加入event里面去处理
我们来比较invokeLater和invokeAndWait的源码区别
/**
* invokeLater将我们的runnable包装成事件丢进eventQueue就没有管了
*/
public static void invokeLater(Runnable runnable) {
Toolkit.getEventQueue().postEvent(
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}
static void invokeAndWait(Object source, Runnable runnable)
throws InterruptedException, InvocationTargetException
{
if (EventQueue.isDispatchThread()) {//这里是防止在EDT里面调用
throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
}
class AWTInvocationLock {}
Object lock = new AWTInvocationLock();
InvocationEvent event =
new InvocationEvent(source, runnable, lock, true);
//这里是关键,当线程进入这里之后获得了锁,然后wait了
synchronized (lock) {
Toolkit.getEventQueue().postEvent(event);
lock.wait();
}
Throwable eventThrowable = event.getThrowable();
if (eventThrowable != null) {
throw new InvocationTargetException(eventThrowable);
}
}
invokeAndWait的时候wait了,然后看在是什么地方notify的,InvocationEvent里面
/**
* Executes the Runnable's <code>run()</code> method and notifies the
* notifier (if any) when <code>run()</code> has returned or thrown an exception.
*
* @see #isDispatched
*/
public void dispatch() {
try {
if (catchExceptions) {
try {
runnable.run();
}
catch (Throwable t) {
if (t instanceof Exception) {
exception = (Exception) t;
}
throwable = t;
}
}
else {
runnable.run();
}
} finally {
dispatched = true;
if (notifier != null) {
synchronized (notifier) {
notifier.notifyAll();//执行完我们的操作后,notify所有线程
}
}
}
}
这就是为什么说invokeAndWait这个方法是同步的原因,调用这个方法的线程会一直堵塞知道执行完毕
现在假如我们在EDT里面调用invokeAndWait,务必会造成死锁,看看下一面这个例子
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class TestAction extends JFrame {
private static final long serialVersionUID = -7462155330900531124L;
private JButton jb1 = new JButton("确定");
private JTextField txt = new JTextField(10);
public TestAction() {
jb1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String name = ((JButton) e.getSource()).getText();
txt.setText(name);
}
});
setLayout(null);
add(txt);
add(jb1);
txt.setBounds(50, 100, 200, 30);
jb1.setBounds(270, 100, 70, 30);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SwingConsole.run(new TestAction(), 500, 500);
}
});
}
}
import javax.swing.*;
public class SwingConsole {
public static void run(final JFrame f, final int width, final int height) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.setTitle(f.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(width, height);
f.setVisible(true);
}
});
}
}
这样写是没有错误的~~假如我们用invokeAndWait,肯定就死锁了
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
public class SwingConsole {
public static void run(final JFrame f, final int width, final int height){
try {
SwingUtilities.invokeAndWait(new Runnable(){
public void run(){
f.setTitle(f.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(width, height);
f.setVisible(true);
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这部分还有一个重点,不要在EDT里面调用费时间的操作,这样会造成界面卡主
来看一个例子,我们模拟10个文件有序上传,并且显示进度条
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class TestProgress extends Thread implements ActionListener {
private static JProgressBar progressBar;
JFrame jf = new JFrame("Test");
JPanel jp = new JPanel();
JTextArea jta = new JTextArea();
JButton jb = new JButton("点击");
public static void main(String[] args) {
new TestProgress();
}
public TestProgress() {
jp.setLayout(new FlowLayout());
progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
jf.add(jp, BorderLayout.NORTH);
jf.add(new JScrollPane(jta));
jp.add(progressBar);
jp.add(jb);
jf.add(new JScrollPane(jta), BorderLayout.CENTER);
jb.addActionListener(this);
jf.setSize(300, 200);
jf.setLocation(300, 200);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void run() {
for (int i = 0; i < 10;i++) {//10个文件
UpLordTread lordTread = new UpLordTread(progressBar,jta,"文件" + i);
lordTread.start();//启动上传程序
try {
lordTread.join();//这就是关键~~等待这个线程完成
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void actionPerformed(ActionEvent e) {
String comm = e.getActionCommand();
if ("点击".equals(comm)) {
this.start();//不能在EDT线程里面执行费时的操作,防止UI卡死
jb.setEnabled(false);
}
}
}
/**
* 文件上传线程
* @author yellowbaby
*
*/
class UpLordTread extends Thread{
JTextArea jta;
JProgressBar progressBar;
public UpLordTread(JProgressBar progressBar,JTextArea jta,String fileName) {
super(fileName);
this.jta = jta;
this.progressBar = progressBar;
}
public void run() {
for (int i = 0; i <= 100; i++) {
progressBar.setValue(i);
String temp = Thread.currentThread().getName() + ":" + i + "\n";
jta.append(temp);
try {
Thread.sleep(10);
} catch (Exception ee) {
ee.printStackTrace();
}
}
progressBar.setValue(0);
}
}
我们点击按钮后,我并没有,在actionPerformaed里面直接调用上传的循环代码,而是从新开了一个线程,去执行,这是为什么呢?
因为actionPerformaed的代码是由EDT调用的,如果这个方法不立即返回的话,EDT线程就无法去处理其他事件,界面也就卡死了
可以试试把代码改成这样,点击按钮后界面就会卡死,然后就只有等到全部上传后界面才恢复
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class TestProgress extends Thread implements ActionListener {
private static JProgressBar progressBar;
JFrame jf = new JFrame("Test");
JPanel jp = new JPanel();
JTextArea jta = new JTextArea();
JButton jb = new JButton("点击");
public static void main(String[] args) {
new TestProgress();
}
public TestProgress() {
jp.setLayout(new FlowLayout());
progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
jf.add(jp, BorderLayout.NORTH);
jf.add(new JScrollPane(jta));
jp.add(progressBar);
jp.add(jb);
jf.add(new JScrollPane(jta), BorderLayout.CENTER);
jb.addActionListener(this);
jf.setSize(300, 200);
jf.setLocation(300, 200);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void run() {
for (int i = 0; i < 10;i++) {//10个文件
UpLordTread lordTread = new UpLordTread(progressBar,jta,"文件" + i);
lordTread.start();//启动上传程序
try {
lordTread.join();//这就是关键~~等待这个线程完成
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void actionPerformed(ActionEvent e) {
String comm = e.getActionCommand();
if ("点击".equals(comm)) {
//this.start();//不能在EDT线程里面执行费时的操作,防止UI卡死
for (int i = 0; i < 10;i++) {//10个文件
UpLordTread lordTread = new UpLordTread(progressBar,jta,"文件" + i);
lordTread.start();//启动上传程序
try {
lordTread.join();//这就是关键~~等待这个线程完成
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
jb.setEnabled(false);
}
}
}
/**
* 文件上传线程
* @author yellowbaby
*
*/
class UpLordTread extends Thread{
JTextArea jta;
JProgressBar progressBar;
public UpLordTread(JProgressBar progressBar,JTextArea jta,String fileName) {
super(fileName);
this.jta = jta;
this.progressBar = progressBar;
}
public void run() {
for (int i = 0; i <= 100; i++) {
progressBar.setValue(i);
String temp = Thread.currentThread().getName() + ":" + i + "\n";
jta.append(temp);
try {
Thread.sleep(10);
} catch (Exception ee) {
ee.printStackTrace();
}
}
progressBar.setValue(0);
}
}
所以我们的费时操作都要丢到背后线程去处理,JDK里面有一个SwingWork可以帮我们处理这个问题
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class TestProgress extends Thread implements ActionListener {
private static JProgressBar progressBar;
JFrame jf = new JFrame("Test");
JPanel jp = new JPanel();
JTextArea jta = new JTextArea();
JButton jb = new JButton("点击");
public static void main(String[] args) {
new TestProgress();
}
public TestProgress() {
jp.setLayout(new FlowLayout());
progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
jf.add(jp, BorderLayout.NORTH);
jf.add(new JScrollPane(jta));
jp.add(progressBar);
jp.add(jb);
jf.add(new JScrollPane(jta), BorderLayout.CENTER);
jb.addActionListener(this);
jf.setSize(300, 200);
jf.setLocation(300, 200);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
String comm = e.getActionCommand();
if ("点击".equals(comm)) {
SwingWorker<Void, Void> swingWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < 10; i++) {// 10个文件
UpLordTread lordTread = new UpLordTread(progressBar,
jta, "文件" + i);
lordTread.start();// 启动上传程序
try {
lordTread.join();// 这就是关键~~等待这个线程完成
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void done() {
System.out.println("上传成功");
}
};
swingWorker.execute();
jb.setEnabled(false);
}
}
}
/**
* 文件上传线程
*
* @author yellowbaby
*
*/
class UpLordTread extends Thread {
JTextArea jta;
JProgressBar progressBar;
public UpLordTread(JProgressBar progressBar, JTextArea jta, String fileName) {
super(fileName);
this.jta = jta;
this.progressBar = progressBar;
}
public void run() {
for (int i = 0; i <= 100; i++) {
progressBar.setValue(i);
String temp = Thread.currentThread().getName() + ":" + i + "\n";
jta.append(temp);
try {
Thread.sleep(10);
} catch (Exception ee) {
ee.printStackTrace();
}
}
progressBar.setValue(0);
}
}
SwingWork里面有两个主要的方法,doInBackground和done,doInBackground就是我们的背后线程,done是做完doInBackground后调用的,主要用来更新UI
转载于:https://blog.51cto.com/2925665/1324538