很多学JAVA程序员都是从Swing开始的但很多人对AWT GUI线程的机制并没有太深的了解或者说一直都只了解线程的概念而不了解AWT对线程的使用我发现很多人碰到线程阻塞的问题就通过调用 SwingUtilitiesinvokeLater()来解决 其实这是很容易造成误会的地方 不要以为Swing 是多线程的实际上Swing 的UI是单线程的 不要以为SwingUtilities的两个invoke是多线程实际上它还是单线程的 不要以为invokeLater的意思是当前线程执行完再执行目标线程以为invokeAndWait的意思是等待目标线程执行完再执行当前线程实际上压根就不是那么回事 问题代码:大意是在按下某个按钮的时候调用一个远程服务 JButton button = new JButton() buttonaddActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { invokeRemoteService()//可能需要等待 } }) 在swing系统中有一个顶级的javaawtContainer(可能是一个JFrame或JDialog实例)负责启动一个EventDispatchThread线程单线程这个线程是负责处理UI事件的 首先界面Swing控件向EventDispatchThread的EventQueue提交一个event由 EventDispatchThread负责调度各个event的执行例如按下一个JButton的时候JButton向EventQueue执行 postEvent提交一个ActionEventEventDispatchThread线程根据调度算法执行到该event的时候会调用 JButton上的processActionEventJButton再调用actionPerformed这过程并没有执行任何new Thread()start()代码也就是说JButton的ActionListeneractionPerformed()中的代码完全是在 EventDispatchThread线程内执行的 所以假如我们在任何ActionListenerMouseListener等对象中编写耗时的逻辑那么整个Swing系统就会出现响应迟钝的现象更有甚者如果在这些Listener中执行线程wait()以等待另一个线程的锁定资源或计算结果那么实际上就是 EventDispatchThread线程被阻塞整个系统界面就会处于无响应状态一点反应都没有 以上是误解造成的了解这个过程就很容易看出上面这段代码的问题是什么原因了解决的方法也倒比较简单直接new Thread()start()就可以保证EventDispatchThread执行到当前方法的时候快速返回以便可以去响应来自用户界面的其他事件 问题代码:大意是在按下某个按钮的时候调用一个远程服务同时处理其他事情 JButton button = new JButton() buttonaddActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { //位置A SwingUtilitiesinvokeLater(new Runnable() { public void run() { //位置B invokeRemoteService()//可能需要等待 } }) doOtherThing() } }) 这段代码跟第一段代码唯一的差别是doOtherThing()在invokeRemoteService ()完成之前就能够得到执行所以造成了invokeRemoteService ()/doOtherThing()好像是在两个线程里执行的假象实际上invokeLater是把目标代码打包成一个Event提交到 EventQueue去了等到EventDispatchThread线程执行完当前代码段的doOtherThing()后再去执行这个 EventQueue中的Event这时候就会执行到这个invokeRemoteService ()方法但是实际上这两个方法都是在EventDispatchThread中执行的并没有任何其他Thread来执行于是问题的问题还是没解决实际上直接new Thread()start()方法就可以了使用SwingUtilities完全是由于误解造成的滥用 测试方法在位置A和位置B都加上下面这行代码 Systemoutprintln(ThreadcurrentThread()getId() + ThreadcurrentThread()getName()) 返回的结果都是一样的 AWTEventQueue AWTEventQueue [讨论] 一般情况下(除了系统启动时后台创建的Daemon线程)系统的所有执行功能逻辑和业务逻辑的线程都应该是从界面操作触发的我们应该清楚哪些需要或应该放到EventDispatchThread中去执行哪些需要或应该创建一个新线程去执行也需要清醒的知道自己当前编写的是属于什么逻辑 这个问题我觉得应该把代码分成层第一层UI层包括UI控件上的Listener逻辑这是应该给EventDispatchThread 去执行的必须简短高效快速return;这一层做不完的事情通过new Thread()start()交给下一层去做我称之为控制层然后控制层再去调用具体的业务代码即第三层业务层所有由UI控件触发的逻辑都应该这么分 另一个问题是Swing并不推荐在EventDispatchThread之外修改界面那么如果我们在业务层需要repaint某个控件或者updateUI应该怎么办呢那就可以使用SwingUtilities来处理了这才是正确使用SwingUtilities的场景也是设计这个工具的目的 |