简析Java Swing线程模型

        Java Swing的线程模型是一个基于事件队列的单线程模型。任何时候运行一个Swing应用程序,都会自动创建3个线程。第一个是应用程序的主线程,它运行应用程序的主方法,主方法一旦运行结束,该线程就消亡了。第二个是工具包线程,它负责捕获系统产生的各种事件。这个线程是AWT的实现的一部分,它也是相当重要的,但是它从来都不会去执行应用的代码,只把捕获到的事件发送给第三个线程,因此编写Swing应用一般不用去考虑该线程。最后一个就是事件分配线程(EDT),这个线程对于编写Swing应用的程序员来说相当重要。它负责把工具包线程捕获到的事件分派给适当的组件,并调用它们相应的绘制方法。同时与Swing进行的交互也是通过EDT,例如在一个JButton上面点击鼠标,就会产生ActionEvent,而该事件则由EDT分派给对应的监听器,并执行对应监听器的对应方法。如果一个组件需要重绘来更新自己的显示,它会产生一个重绘请求并把请求加入到事件队列中。当EDT从事件队列中获取到该重绘请求时,它就会调用对应组件的重绘方法来重新绘制该组件。因此,Swing中的事件的处理和组件的更新都依赖于EDT。

        Swing的单线程模型并不复杂。但如果编写Swing应用的程序员并不了解该模型,就很可能编写出效率低下、响应缓慢,甚至卡死的图形界面程序。EDT几乎负责了与Swing有关的所有事情,因此如果在EDT上面执行长时间的计算操作或者长时间的I/0操作,就很可能会导致Swing响应缓慢,甚至于卡死。例如,一个刚接触Swing的人很可能会写出这样的代码:

public class Bad extends JFrame{
    private JButton button;
    public Bad(){
        button = new JButton("读取");
        button.addActionListener(new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                //进行长时间I/0操作,比如读取一个大文件。
            }
            
        });
        add(button,BorderLayout.SOUTH);
        setSize(800,600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }
    
    public static void main(String[] args){
        Bad bad = new Bad();
    }
}

该程序当点击按钮之后,EDT就会被长时间的I/O操作给阻塞了。于是事件的分派跟界面的更新都被阻塞了。在用户看来,这样的程序响应很慢,甚至于就像中止了一样。有些人不了解Swing的线程机制,觉得Swing的效率很低。但事实上Swing组件非常快速地分派它们的工作。只有当应用程序有某项任务阻塞了EDT才会导致Swing的响应缓慢。因此,编写Swing应用的时候,不要在EDT上执行长时间的计算任务或者I/O操作,在EDT上执行的任务尽可能短小快速,避免阻塞EDT。当需要执行一项长时间的任务时,应该新建一个工作线程来处理。比如上面提到的那段代码,可以改成这样:

public class Good extends JFrame{
    private JButton button;
    public Good(){
        button = new JButton("读取");
        button.addActionListener(new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                //新建一个工作线程来处理长时间任务
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //进行长时间I/0操作,比如读取一个大文件。
                    }

                });
                t.start();
            }

        });
        add(button,BorderLayout.SOUTH);
        setSize(800,600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }
    
    public static void main(String[] args){
        Good bad = new Good();
    }
}

        还有Swing不是一个线程安全的API,它遵循一个单一的规则:EDT负责执行所有改变组件状态的方法,包括组件的构造方法。由于Swing不是线程安全的,所以不在EDT上面更新组件的状态,可能会破坏组件的数据和状态。如果当某个工作线程完成了一项长时间的任务,然后需要更新组件的状态的时候,我们可以使用EventQueue.invokeLater方法来向EDT发送一项新的任务。该任务会进入事件队列等待EDT的执行。invokeLater的用法如下:

EventQueue.invokeLater(new Runnable(){
      @Override
      public void run(){
          //要在EDT上执行的任务
      }
});

        总的来说,Swing的线程模型是单线程的。EDT是其核心。编写Swing应用时,不要在EDT上执行耗时很长的任务,耗时很长的任务应该通过工作线程来完成。同时Swing API不是线程安全的,要遵守单一的规则,即EDT负责完成所有改变组件状态的方法。

你可能感兴趣的:(java,swing)