Swing笔记 2. SwingUtilities , invokeLater , 线程安全

               SwingUtilities 和线程安全

多线程是图形界面程序的本质特征,几乎所有图形界面程序都要响应按钮、文本框、菜单等控件触发的事件,为了完成这些任务,必须使用多线程。

对于不熟悉多线程程序的人来说,需要一段时间来理解和适应多线程的程序设计。

Swing规范要求,在Swing控件可见以后,所有对其外观的修改都应当采用如下的形式:

    	SwingUtilities.invokeLater(new Runnable(){
    		public void run() { 
                      // ...
                      // 修改GUI控件的外观
    		}
    	}) ;

并说明,“由于swing不是线程安全的,不采用这种方式可能会出现意想不到的错误...“

对于不太了解Swing的人来说,这个要求真可以说是莫名其妙,在我对此感到莫名其妙的时候,我也比较好奇,会产生什么意想不到的错误呢?

要说明这个问题,可以用一个具体的例子配合实验来说明。 假设我们用swing实现如下一个应用:

名称:通知

说明:这个swing程序将在启动后,访问网络,将网络上以某种加密格式存储的通知信息显示出来。

界面示例:  

 

ok

上图中"用户需要的数据"就是通知内容。

经过研究,我们决定使用JTextArea作为显示通知的控件,从网络上拿到通知文本以后,使用如下JTextArea指令将通知内容显示出来:

data.setText(str) ;

一个看起来很简单的应用。 可是在我们进行了实现,并做了一些优化以后,却发现时不常的,我们的通知内容不能显示出来,初步调查显示,在无法显示通知时,java控制台会出现一条异常:

 

  err

这也许就是传说中的意想不到的错误。

下面我们将使用最多不超过80行的本地程序来模拟这个错误的呈现。

最初我们做了如下实现:

import javax.swing.*;

public class taskman_serial {
    
    taskman_serial() {
        // 从网上获取通知内容   
        String str = "这是用户需要的数据" ;   
        
        JTextArea data = new JTextArea(str) ;
        JFrame frame = new JFrame("任务人II") ;
        frame.setSize(300, 200) ;
        frame.setDefaultCloseOperation
        (JFrame.EXIT_ON_CLOSE) ;
        frame.setContentPane(data) ;
        
        frame.setVisible(true) ;
    }

    public static void main(String[] args) 
    throws Exception{
        String lnf = UIManager   
        .getCrossPlatformLookAndFeelClassName() ;   
        UIManager.setLookAndFeel(lnf) ;   
        JFrame.setDefaultLookAndFeelDecorated(true) ;   
       
        new taskman_serial() ;
    }
}

 

运行这个程序,看起来完美的实现了设计要求。

但是,在用户使用了一段时间以后,却提出了一个设计要求之外的需求:

有时候,获取网络上的通知信息需要十几秒甚至更长的时间,而在这段时间内,屏幕一直没有反映,让人感觉程序没有启动,结果重复的双击图标,一会儿出现了一大堆通知窗口。

ok,我们改进程序如下:

1、启动后立即显示程序窗口,通知区域显示”正在获取通知“;

2、装载通知以后,再将通知显示出来

看来肯定需要线程来帮忙了,

1、我们分别启动两个线程,一个获取数据,一个创建gui,

2、GUI创建完成以后马上显示出来,而得到数据以后,再更新数据区域,

3、为了使两个线程都可以访问JTextArea,我们把它定义为taskman的属性变量

为了模拟耗时的网络操作,我们编写了子程序op,用于模拟访问网络消耗的毫秒数:

    void op(long millis) { 
        try { 
            Thread.sleep(millis) ;
        } catch (Exception exc) {}
    }

 

完整代码如下:

import javax.swing.*;

public class taskman_thread {
    JTextArea data ;
    
    taskman_thread() {
        Thread t_ui = new Thread() { 
            public void run() { 
                make_ui() ;
            }
        } ;
        Thread t_load = new Thread() { 
            public void run() { 
                load_data() ;
            }
        } ;
        
        t_ui.start() ;
        t_load.start() ;
    }

    void make_ui() { 
        data = new JTextArea("正在获取数据...") ;
        JFrame frame = new JFrame("任务人II") ;
        frame.setContentPane(data) ;
        frame.setDefaultCloseOperation
        (JFrame.EXIT_ON_CLOSE) ;
        frame.setSize(300, 200) ;
        frame.setVisible(true) ;
    }
    
    void load_data() { 
        // 用3000毫秒时间访问网络,将得到的数据显示在控件中
        op(3000) ;
        data.setText("这是用户需要的数据") ;
    }
    
    void op(long millis) { 
        try { 
            Thread.sleep(millis) ;
        } catch (Exception exc) {}
    }

    public static void main(String[] args) 
    throws Exception{
        String lnf = UIManager   
        .getCrossPlatformLookAndFeelClassName() ;   
        UIManager.setLookAndFeel(lnf) ;   
        JFrame.setDefaultLookAndFeelDecorated(true) ;   

        new taskman_thread() ;
    }
}

 

经运行,达到预期要求,程序在启动后立即显示了GUI画面,并在稍后完成了网络访问,刷新了通知区域

 

噩梦是在一个管理需求后开始的,需求说,程序需要在启动后,要到中心服务器获取用户的权限,如果用户可以发布通知,界面上要有发布通知的控件,否则,界面仍然和原来一样。

 只须在子程序make_ui()的首部增加语句op(4000),就可以模拟这个问题:

 

    void make_ui() { 
        op(4000) ;
        // 获取用户权限信息
         data = new JTextArea("正在获取数据...") ;
        JFrame frame = new JFrame("任务人II") ;
        frame.setContentPane(data) ;
        frame.setDefaultCloseOperation
        (JFrame.EXIT_ON_CLOSE) ;
        frame.setSize(300, 200) ;
        frame.setVisible(true) ;
    }

 

修改其中op()的参数,如果是2000,肯定不会有问题,如果大于3000,就会出问题。

 

问题显而易见,由于make_ui的执行时间延长了,当得到了数据但JTextArea还未完成初始化时,就会出现问题。

 

好了,如果是这样,该用SwingUtilities了,是否应该这样写代码:

    taskman_ok() {
        Thread t_ui = new Thread() { 
            public void run() { 
                make_ui() ;
            }
        } ;
        Thread t_load = new Thread() { 
            public void run() { 
                load_data() ;
            }
        } ;
        
        SwingUtilities.invokeLater(t_ui);
        SwingUtilities.invokeLater(t_load) ;
    }

运行一下结果正常了,时间长了很多,原来4秒可以完成通知的下载和显示,现在需要7秒了,make_ui()和load_data()实际上又变成了串行执行。

再改进,仅把与GUI相关的指令序列放进SwingUtilities,这回OK了,既可以最快的打开界面,又可以并行的获取数据了。

完整代码如下:

import javax.swing.*;

public class taskman_ok {
    JTextArea data ;
    
    taskman_ok() {
        Thread t_ui = new Thread() { 
            public void run() { 
                make_ui() ;
            }
        } ;
        Thread t_load = new Thread() { 
            public void run() { 
                load_data() ;
            }
        } ;
        
        SwingUtilities.invokeLater(t_ui);
        t_load.start() ;
    }

    void make_ui() { 
        op(4000) ;
        // 获取用户权限信息
         data = new JTextArea("正在获取数据...") ;
        JFrame frame = new JFrame("任务人II") ;
        frame.setContentPane(data) ;
        frame.setDefaultCloseOperation
        (JFrame.EXIT_ON_CLOSE) ;

        frame.setSize(300, 200) ;
        frame.setVisible(true) ;
    }
    
    void load_data() { 
        // 用3000毫秒时间访问网络,将得到的数据显示在控件中
         op(3000) ;
        SwingUtilities.invokeLater(new Runnable(){
            public void run() { 
                data.setText("这是用户需要的数据") ;
            }
        }) ;
    }
    
    void op(long millis) { 
        try { 
            Thread.sleep(millis) ;
        } catch (Exception exc) {}
    }

    public static void main(String[] args) 
    throws Exception{
        String lnf = UIManager   
        .getCrossPlatformLookAndFeelClassName() ;   
        UIManager.setLookAndFeel(lnf) ;   
        JFrame.setDefaultLookAndFeelDecorated(true) ;   

        new taskman_ok() ;
    }
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值