swing线程机制

在介绍swing线程机制之前,先介绍一些背景概念。

背景概念

同步与异步:

    同步是指程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被阻塞(block)直到请求完成。

    异步是当前程序发起请求后立即返回,当前程序不会立即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理。

串行与并行:

串行是指多个要处理的请求按照顺序执行,处理完一个再处理下一个。

并行可以理解为并发,指的是同时处理多个请求(实际上我们只能理论上这么理解,特别是CPU数目少于线程数的机器而言,真正意义的并发是不存在的,各个线程只是断断续续地交替执行(以消耗CPU时间片的形式))。

队列:

 队列是一种线性的数据结构,元素遵守“先进先出”原则。队列的处理方式是处理完一个再处理下一个。

Swing程序中的线程

 一个swing程序包含三种类型的线程:初始化线程(Initial Thread)、事件分派线程(Event Dispatch Thread)和任务线程(Worker Thread)。

 初始化线程:每个程序都有一个main方法,这是程序执行的入口,该方法运行在初始化线程上。初始化线程读取程序参数并初始化一些对象。在许多Swing程序中,该线程主要目的是启动程序的图形用户界面(GUI)。一旦GUI启动后,对于大多数事件驱动的桌面程序来说,初始化线程的工作就结束了,程序的控制权就交给了UI。

 事件分派线程(EDT ) :主要负责GUI组件的绘制和更新,并响应用户的输入。每个EDT都会负责管理一个事件队列(EventQueue),用户每次对界面更新的请求(包括键盘、鼠标等事件)都会排到事件队列中,然后等待EDT的处理。

 任务线程:主要负责执行和界面无直接关系的耗时任务和输入/输出密集型操作,即任何干扰或延迟UI事件的处理都应该由任务线程来完成。注意,任务线程是通过javax.swing.SwingWorker类显式启动的。

EDT线程注意事项

一、任何GUI的请求都必须由EDT线程来处理

 EDT线程将所有的GUI组件绘制和更新请求以及事件请求都放入了一个事件队列中。队列这种数据结构前面也讲过了,它是线性、“先进先出”的。所以,通过事件队列的机制,就可以将并发的GUI请求转化为事件队列,从而按顺序处理。这样就可以保证线程安全。所以说,尽管大多数swing API本身不是线程安全的,但是swing通过EDT线程和事件队列机制实现了保障线程安全。

 同理,不建议从其他线程直接访问UI组件及其事件处理器,这会破坏线程安全的保障,可能会导致界面更新和绘制错误。

二、在非EDT线程中通过invokeLater和invokeAndWait方法向EDT线程的事件队列添加GUI请求

 有的时候需要在一个非EDT线程中调用swing API来处理GUI请求,根据第一条注意事项,显然我们不能直接访问GUI组件。这时候我们可以调用这两个方法将GUI请求添加到EDT线程的事件队列中。

 举个例子:我们有一个类A继承了JFrame,如何在main方法中正确的启动GUI呢?我们知道main方法属于初始化线程,这就是典型的非EDT线程访问GUI组件的问题。

 错误的启动方式为: new A();

 如果这么做了,就相当于在非EDT线程中直接访问了GUI组件,这破坏了线程安全。

 正确的启动方式为:

  SwingUtilities.invokeLater(new Runnable() {
     public void run() {
      createGUI();
     }
   });

  通过invokeLater和invoke方法,可以从一个非EDT线程中,将GUI请求添加到EDT线程的事件队列中去。

  这两个方法的区别是:invokeLater是异步的,调用该方法时,该方法将GUI请求添加到事件队列中后直接返回。InvokeAndWait是同步的,调用该方法时,该方法将GUI请求添加到事件队列中后,会一直阻塞,直到该请求被完成后才会返回。

三、耗时操作应放到任务线程中,通过SwingWorker启动任务线程

 EDT的事件队列的机制在保障了线程安全的同时,也引入了一个新的问题:假设事件队列中某一个GUI请求执行时间非常长,那么由于队列的特点,队列中的后续GUI请求都会被阻塞。在实际的应用程序中,表现为:点击了一个按钮触发了耗时任务后其他的组件都失去响应,必须等待该任务完成界面才能恢复响应。

我们用一个简单的程序测试一下,一个简单的swing小程序。start按钮模拟写入数据(耗时操作),display按钮用于将文本框中的内容输出到文本显示区中。数据写入完成后,在文本框中输出“数据写入完毕”。在点击start按钮后,我连续点击了三次display按钮都没有任何响应。三秒钟之后,响应结果出来了,文本显示区中输出了三行“数据写入完毕”。用户体验极差。

代码如下:

package swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestEDT {
    public static void createGUI() {
        JFrame frame=new JFrame("swing线程机制");
        JTextField tf=new JTextField("hello world");
        JTextArea ta=new JTextArea();
        ta.setEditable(false);
        JButton b1=new JButton("start");
        JButton b2=new JButton("display");
        JPanel p1=new JPanel();
        JPanel p2=new JPanel();
        p1.setLayout(new BorderLayout());
        p2.add(b1);
        p2.add(b2);
        p1.add(ta,BorderLayout.CENTER);
        p1.add(tf,BorderLayout.NORTH);
        p1.add(p2,BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(p1);
        frame.pack();
        frame.setVisible(true);
        //start按钮开始写入数据,该操作耗时很久
        b1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //用线程休眠方法模拟耗时的写入数据操作
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                tf.setText("数据写入完毕");
            }
        });
        //display按钮用于将文本框中的信息输出到文本显示区中
        b2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ta.append(tf.getText()+"\n");
            }
        });
    }
    //用正确的方式启动GUI
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createGUI();
            }
        });
    }
}

考虑到用户体验性,应使用独立的任务线程来执行耗时计算或输入输出密集型任务,比如同数据库通信、访问网站资源、读写大数据量的文件等操作。

四、千万别在EDT线程中调用invokeAndWait方法

 在非EDT线程中,调用invokeAndWait方法可以很好地将GUI请求添加到EDT线程的事件队列中。但是如果在EDT线程中调用该方法会发生死锁。

 这是因为如果在EDT线程中调用invokeAndWait方法,GUI请求被添加到了事件队列中后,invokeAndWait方法根据其特性,会一直等待直到EDT线程执行完自己run方法中的请求为止。但是对于队列而言,默认请求是被添加到尾部的。EDT线程根据到达不了该请求的位置,因为它现在的请求也就是invokeAndWait还没有执行完。

 简而言之,就是:EDT线程必须完成该方法后才能去完成该GUI请求,但是必须先完成该GUI请求才能完成该方法。这样双方互相等待,产生了死锁。

任务线程的用法

任务线程是需要显示地通过SwingWorker类调用的。

泛型参数<T,V>的分别代表:T 是 此 SwingWorkerdoInBackgroundget 方法返回的结果类型;V 是用于保存此 SwingWorkerpublishprocess 方法的中间结果的类型。

顾名思义,该类的doInBackground()方法表示在后台执行的方法,由任务线程完成,用于执行耗时操作的代码;done()方法是doInBackground方法执行完成后再调用的方法,方法体中的内容交付给EDT线程,用于处理GUI请求。

仍然以之前的模拟“写入数据”程序为例,演示任务线程的用法。

代码如下:

package swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestEDT {
    public static void createGUI() {
        JFrame frame=new JFrame("swing线程机制");
        JTextField tf=new JTextField("hello world");
        JTextArea ta=new JTextArea();
        ta.setEditable(false);
        JButton b1=new JButton("start");
        JButton b2=new JButton("display");
        JPanel p1=new JPanel();
        JPanel p2=new JPanel();
        p1.setLayout(new BorderLayout());
        p2.add(b1);
        p2.add(b2);
        p1.add(ta,BorderLayout.CENTER);
        p1.add(tf,BorderLayout.NORTH);
        p1.add(p2,BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(p1);
        frame.pack();
        frame.setVisible(true);
        //start按钮开始写入数据,该操作耗时很久
        b1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new SwingWorker<Integer,Void>(){
                    protected Integer doInBackground() {
                        //模拟写入数据这一耗时操作
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return 1;
                    }
                    protected void done() {
                        tf.setText("数据写入完毕");
                    }
                }.execute();
            }
        });
        //display按钮用于将文本框中的信息输出到文本显示区中
        b2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ta.append(tf.getText()+"\n");
            }
        });
    }
    //用正确的方式启动GUI
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createGUI();
            }
        });
    }
}

测试结果为:点击start按钮后,其他的组件仍然有良好的响应。三秒钟之后,文本框内容成功显示为“数据写入完毕”。

总结一下,swing线程机制的注意事项有:

1、所有的GUI请求必须都由EDT线程完成(保障线程安全),不建议通过非EDT线程访问GUI组件

2、非EDT线程通过invokeLater和invokeAndWait方法将GUI请求交付给EDT线程。

3、禁止在EDT线程中调用invokeAndWait方法(造成死锁)。

4、耗时操作由任务线程执行,通过SwingWorker类显示启动任务线程。

over。

 

时间: 2024-09-29 02:09:04

swing线程机制的相关文章

一步一步掌握线程机制(六)---Atomic变量和Thread局部变量

一步一步掌握线程机制(六)---Atomic变量和Thread局部变量 前面我们已经讲过如何让对象具有Thread安全性,让它们能够在同一时间在两个或以上的Thread中使用.Thread的安全性在多线程设计中非常重要,因为race condition是非常难以重现和修正的,我们很难发现,更加难以改正,除非将这个代码的设计推翻来过. 同步最大的问题不是我们在需要同步的地方没有使用同步,而是在不需要同步的地方使用了同步,导致效率极度低下.所以,我们要想办法限制同步,因为无谓的同步比起无谓的运算还更

线程机制、CLR线程池以及应用程序域

最近在总结多线程.CLR线程池以及TPL编程实践,重读一遍CLR via C#,比刚上班的时候收获还是很大的.还得要多读书,读好书,同时要多总结,多实践,把技术研究透,使用好. 话不多说,直接上博文吧.先说一下,为什么Windows要支持线程机制? 1. Windows为什么要支持线程 计算机的早期时代,操作系统没有线程的概念,整个系统只运行着一个执行线程,其中包含操作系统代码和应用程序代码.只用一个执行线程的问题在于,长时间运行的任务会阻止其他任务的执行.例如16位Windows的时代,打印文

Java线程机制

线程简介: 线程是一个程序内部的顺序控制流.线程和进程的区别: 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销. 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换的开销小. 多进程: 在操作系统中能同时运行的多个任务. 多线程: 在同一个应用程序中有多个顺序流同时执行. Java的线程是通过java.lang.Thread类实现的:JVM启动时会有一个由主方法所定义的线程:可以通过创建Thread的实例来创建新的线

从setTimeout到浏览器线程机制 ,实现JS线程和UI同时执行的效果

遇到一个问题情况: ocx读取多条记录的结果集. 在js里用 for遍历. for(var i= 0;i<length;i++){ $.ajax({ 后台返回结果 处理成功, 调用更新进度条的方法. }) } 发现,总是当for全部遍历完成,才去渲染进度条控件.更改样式. 查阅N多资料更改后, 将for改为递归调用,没执行一次,渲染一次进度条,后面的JS代码放在setTimeout(function,0),这时浏览器会优先渲染UI界面,然后在执行后面的JS代码,就实现了进度条实时更新了. //更

并发编程—— Java 内建线程机制【上】

不理解多线程程序设计, 就无法真正理解 JavaSE 和 JDK 源码: 因为线程特性已经与Java 语言紧密地融合在一起. 如何学习多线程程序设计呢? 那看上去似乎总是显得有些神秘.首先, 必须透彻理解并发程序设计的基本原理和机制, 否则, 只是学习使用那些关键字.类的招式,恐怕只能获得Superficial 的认识, 因为多线程程序设计的难点就在于,在任何情况下都能正确工作, easily writing programs that appear to work but will fail

java并发编程——基本线程机制1

一.为什么需要并发编程 如果是单线程的编程,如果一个程序遇到阻塞的情况,比如需要等待i/o的某个事件发生,才能执行程序.这样就造成了影响了下面的程序的运行. 并发,就是在进程中,采用多个任务进行处理,每个任务由操作系统来回切换. 这样就感觉像很多任务同时执行一样. 二.基本的线程机制 1.定义任务 定义一个类,实现Runnable()接口,在Runnable()接口中定义了run()方法,我们可以把要执行的事件写在run()方法中. 而run()中任务的运行,需要将其放在Thread构造器中.

Vue.js线程机制问题还是数据双向绑定有延迟的问题

最近用select2做一个下拉多选,若只是从后端获取一个列表渲染还好说,没有任何问题.但要用select2对数据初始化时进行selected的默认选项进行显示,就出现问题了. vm.$set('areas', data.data); areaIdsSelect2(); areaIdsSelect2Change(); 区域没有显示出默认的selected.此时做一个定时器, vm.$set('areas', data.data); setTimeout(function () { areaIdsS

Android线程机制浅析(ppt)

Android线程机制浅析(ppt)

基础线程机制--Executor线程池框架

基础线程机制 Executor线程池框架 1.引入Executor的原因 (1)new Thread()的缺点 ???每次new Thread()耗费性能 ???调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,之间相互竞争,导致过多的系统资源被占用导致系统瘫痪,不利于定时执行,定期执行,线程中断. (2)采用线程池的优点 ???可以重用创建的线程,减少对象的创建,消亡的开销,性能更佳. ???可以有效的控制最大并发线程数,提高系统资源的利用率,避免过多的资源竞