Why Isn’t the Swing Toolkit Multithread-Safe?
After Swing components have been displayed on the screen, they should only be operated on by the event-handling thread. The event-handling thread (or just event thread) is started automatically by the Java VM when an application has a graphical interface. The event thread calls methods like paint() on Component, actionPerformed() on ActionListener, and all of the other event-handling methods.
当Swing组件显示在屏幕上之后,只应该被消息处理线程操作。当应用程序具有图形界面时,java虚拟机会自动创建消息处理线程,负责调用Component的paint()方法,ActionListener的actionPerformed()方法等等消息处理方法。
Most of the time, modifications to Swing components are done in the event-handling methods. Because the event thread calls these methods, it is perfectly safe to directly change components in event-handling code. SimpleEvent (see Listing 9.1) shows safe Swing code.
/*
* Created on 2005-7-17
*
* Java Thread Programming - Paul Hyde
* Copyright ? 1999 Sams Publishing
* Jonathan Q. Bo 学习笔记
*
*/
package org.tju.msnrl.jonathan.thread.chapter9;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* @author Jonathan Q. Bo from TJU MSNRL
*
* Email:jonathan.q.bo@gmail.com
* Blog:blog.csdn.net/jonathan_q_bo
* blog.yesky.net/jonathanundersun
*
* Enjoy Life with Sun!
*
*/
public class SimpleEvent {
private static void print(String msg){
String temp = Thread.currentThread().getName();
System.out.println(temp + " - " + msg);
}
public static void main(String[] args) {
final JLabel lb = new JLabel("_____");
JButton bt = new JButton("Click here!");
JPanel pn = new JPanel(new FlowLayout());
pn.add(lb);
pn.add(bt);
bt.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
print("do action ... ");
lb.setText("Clicked!");
}
}
);
JFrame fm = new JFrame("simple event");
fm.setContentPane(pn);
fm.setSize(300,100);
fm.setVisible(true);
}
}
控制台输出结果:
AWT-EventQueue-0 - do action ...
表明消息线程在做处理
Using SwingUtilities.invokeAndWait()
The developers of the Swing toolkit realized that there would be times when an external thread would need to make changes to Swing components. They created a mechanism that puts a reference to a chunk of code on the event queue. When the event thread gets to this code block, it executes the code. This way, the GUI can be changed inside this block of code by the event thread.
有时候外部线程(非消息处理线程),需要操作Swing组件。可以把一些代码放到消息队列上,由消息线程执行,这样GUI就可以由外部线程的代码块来改变(仍旧通过调用消息线程)。
The SwingUtilities class has a static invokeAndWait() method available to use to put references to blocks of code onto the event queue:
public static void invokeAndWait(Runnable target)
throws InterruptedException,
InvocationTargetException
An InterruptedException is thrown if the thread that called invokeAndWait() is interrupted before the block of code referred to by target completes. An InvocationTargetException (a class in the java.lang.reflect package) is thrown if an uncaught exception is thrown by the code inside run().
如果在target完成之前,调用invokeAndWait()的线程interrupted,则会抛出InterruptedException
如果run()方法内有未捕获的异常,会抛出InvocationTargetException()异常(属于java.lang.reflect包)
A new thread is not created when Runnable is used with SwingUtilities.invokeAndWait(). The event thread will end up calling the run() method of the Runnable when its turn comes up on the event queue.
注意:虽然这个地方新建了一个Runnable对象,但并不创建新线程来执行它,而是消息线程执行。
/*
* Created on 2005-7-17
*
* Java Thread Programming - Paul Hyde
* Copyright ? 1999 Sams Publishing
* Jonathan Q. Bo 学习笔记
*
*/
package org.tju.msnrl.jonathan.thread.chapter9;
import java.awt.*;
//import java.awt.event.*;//don't need this package
import javax.swing.*;
import java.lang.reflect.*;//just for 'InvocationTargetException'
/**
* @author Jonathan Q. Bo from TJU MSNRL
*
* Email:jonathan.q.bo@gmail.com
* Blog:blog.csdn.net/jonathan_q_bo
* blog.yesky.net/jonathanundersun
*
* Enjoy Life with Sun!
*
*/
public class InvokeAndWait {
private static void print(String msg){
String temp = Thread.currentThread().getName();
System.out.println(temp + " - " + msg);
}
public static void main(String[] args) {
final JLabel lb = new JLabel("_____");
JButton bt = new JButton("Click here!");
JPanel pn = new JPanel(new FlowLayout());
pn.add(lb);
pn.add(bt);
JFrame fm = new JFrame("simple event");
fm.setContentPane(pn);
fm.setSize(300,100);
fm.setVisible(true);
/*
lb.setText("Clicked");
lb.repaint();
*/
/*三秒后自动更改label*/
try{
Thread.sleep(3000);
print("thread sleep 3secs");
print("create a code block to run by event-thread");
Runnable runA = new Runnable(){
public void run() {
print("do change label ...");
lb.setText("Clicked");
}
};
print("begin to run invokeandwait()");
/*交由消息线程处理*/
SwingUtilities.invokeAndWait(runA);
print("end from invokeandwait()");
}catch(InterruptedException e1){
e1.printStackTrace();
}catch(InvocationTargetException e2){
e2.printStackTrace();
}
}
}
控制台输出结果:
main - thread sleep 3secs
main - create a code block to run by event-thread
main - begin to run invokeandwait()
AWT-EventQueue-0 - do change label ...
main - end from invokeandwait()
Do not call SwingUtilities.invokeAndWait() from the event thread. Doing so causes an instance of Error to be thrown. Even if this call were allowed, it would put the event thread into a deadlocked state. The event thread does not need the services of invokeAndWait() because it can make the changes directly.
不要在消息线程内调用SwingUtilities.invokeAndWait(),会抛出一个错误,并且会造成消息线程思索。消息线程本身就可以直接改变GUI,根本不需要调用invokeAndWait()。
Using SwingUtilities.invokeLater()
The SwingUtilities class has another static method available to use to put references to blocks of code onto the event queue:
public static void invokeLater(Runnable target)
The SwingUtilities.invokeLater() method works like SwingUtilities.invokeAndWait() except for the fact that it puts the request on the event queue and returns right away. The invokeLater() method does not wait for the block of code inside the Runnable referred to by target to execute. This allows the thread that posted the request to move on to other activities.
invokeLater()和invokeAndWait()唯一不同之处在于:invokeLater()立刻返回的,不等待run代码执行完成。并且此方法不上抛任何异常,需要及时在target的run内部捕获可能的异常。
Just as with invokeAndWait(), a new thread is not created when Runnable is used with SwingUtilities.invokeLater().
同样,执行此方法并不创建新线程。
/*
* Created on 2005-7-17
*
* Java Thread Programming - Paul Hyde
* Copyright ? 1999 Sams Publishing
* Jonathan Q. Bo 学习笔记
*
*/
package org.tju.msnrl.jonathan.thread.chapter9;
import java.awt.*;
//import java.awt.event.*;//don't need this package
import javax.swing.*;
//import java.lang.reflect.*;//just for 'InvocationTargetException'
/**
* @author Jonathan Q. Bo from TJU MSNRL
*
* Email:jonathan.q.bo@gmail.com
* Blog:blog.csdn.net/jonathan_q_bo
* blog.yesky.net/jonathanundersun
*
* Enjoy Life with Sun!
*
*/
public class InvokeLater {
private static void print(String msg){
String temp = Thread.currentThread().getName();
System.out.println(temp + " - " + msg);
}
public static void main(String[] args) {
final JLabel lb = new JLabel("_____");
JButton bt = new JButton("Click here!");
JPanel pn = new JPanel(new FlowLayout());
pn.add(lb);
pn.add(bt);
JFrame fm = new JFrame("simple event");
fm.setContentPane(pn);
fm.setSize(300,100);
fm.setVisible(true);
/*
lb.setText("Clicked");
lb.repaint();
*/
/*暂停三秒钟*/
try{
Thread.sleep(3000);
print("thread sleep 3secs");
}catch(InterruptedException e1){
e1.printStackTrace();
}
print("create block to run by invokelater()");
Runnable runA = new Runnable(){
public void run(){//需要捕获可能的异常
try{
Thread.sleep(100);
print("set text to label");
lb.setText("clicked!");
}catch(Exception e){
e.printStackTrace();
}
}
};
print("beigin invokeLater() ... ");
SwingUtilities.invokeLater(runA);//此方法立刻返回
print("end from invokeLater() ...");
}
}
控制台输出结果:
main - thread sleep 3secs
main - create block to run by invokelater()
main - beigin invokeLater() ...
main - end from invokeLater() ...
AWT-EventQueue-0 - set text to label
Unlike SwingUtilities.invokeAndWait(), the event thread is permitted to call SwingUtilities.invokeLater(). However, there isn’t any value to doing so because the event thread can change the components directly.
Using SwingUtilities.isEventDispatchThread()
public static boolean isEventDispatchThread()
This static method returns true if the thread that invokes it is the event thread, and returns false if it is not.
此方法测试调用此方法的线程是否是消息线程,如果是,返回true,反之,返回false。
if ( SwingUtilities.isEventDispatchThread() == false ) {
throw new RuntimeException(
“only the event thread should invoke this method”);
}
When invokeAndWait() and invokeLater() Are Not Needed
It is not always necessary to use invokeAndWait() and invokeLater() to interact with Swing components. Any thread can safely interact with the components before they have been added to a visible container. You have seen this already in the examples: The main thread constructs the GUI and then invokes setVisible(). After the components have been drawn to the screen, only the event thread should make further changes to their appearance.
不是必须使用invokeAndWait()方法和invokeLater()方法来和Swing组件交互的。当组件被加到个可见的容器之前,任何线程都可以安全地和其交互。main线程初始化GUI,然后调用setVisible(),当组件显示在屏幕上之后,应该只由消息线程来改变其外观。
There are a couple of exceptions to this restriction. The adding and removing of event listeners can safely be done by any thread at any time. Also, any thread can invoke the repaint() method. The repaint() method has always worked asynchronously to put a repaint request onto the event queue. And finally, any method that explicitly indicates that it does not have to be called by the event thread is safe. The API documentation for the setText() method of JTextComponent explicitly states that setText() can be safely called by any thread. The setText() method is inherited by JTextField (a subclass of JTextComponent), so any thread can safely invoke setText() on a JTextField component at any time.
还有一些特例,任何线程随时都可以添加或删除消息监听器。任何线程都能够调用repaint()方法,此方法异步将repaint请求放到消息队列。并且,任何显式申明为线程安全可以被任何线程调用的方法都是安全的,可以不通过消息线程来调用。就像前面几个例子中的setText()方法,在JTextComponet中说明:“This method is thread safe, although most Swing methods are not. Please see Threads and Swing for more information”,任何线程都可以直接调用,就可以更改控件外观,不需要调用repaint(),不需要invokeAndWait()等等交由消息线程处理。
If you aren’t sure whether a particular method on a Swing component can be invoked by any thread, use the invokeAndWait() or invokeLater() mechanism to be safe.