Java Swing - invokeLater子线程修改主线程UI

问题:
1.我在这里是想解决如何使用SwingUtilities来进行线程通信。
2.扩展一下多线程与子线程的知识,一直对这一块儿比较迷糊
学习:
1.就先来学习一下, Swing线程机制
    大多数SwingAPI是非线程安全的,不能在任意地方调用,只能在EDT中调用。Swing的线程安全靠事件队列和EDT来保障,EventQueue派发机制由单独线程EDT进行管理。 借助于EDT,可以使不具备线程安全的的Swing函数库避免了并发访问的问题。
    Swing程序包括三种类型的线程,初始化线程,任务线程和事件调度线程(EDT)。
   ①初始化线程负责启动程序的UI界面,UI界面完成,初始化线程的工作就结束了,每个程序都是从main方法开始执行,而main方法一般就是运行在初始化线程上。
   ② 任务线程负责执行和界面无直接关系的耗时任务和输入输出的密集型操作,任何延迟UI的处理都会由该线程完成, 任务线程是通过 javax.swing.SwingWorker 类显式启动的。
   ③ EDT主要负责绘制和更新界面,并且响应用户的输入,每个Swing都会有一个EDT,通过EDT管理一个事件队列,用户每次对界面的更新请求都会排到事件队列中等待EDT的处理,EDT会将队列中事件按照顺序派发给相应的事件监听事件,并且调用其中的回调函数。
    EDT管理一个事件队列,并且它们是按照顺序派发的,而事件派发是单线程操作,所以只有在前面事件监听器的回调函数执行完毕,才可以执行组件更新的操作一级继续派发后面的事件,当一个事件监听回调函数中做了耗时的操作就会导致UI停止,并且其上所有控件失效。所以,我们就需要把事件处理函数中耗时操作放到新线程中执行。
这一部分参考博客(其中还有具体用例):
2.编写Swing程序时,需要注意三点: ①不能从其他非EDT线程访问UI组件或者事件处理器,否则会使程序出现非线程安全问题;②不能在EDT中执行耗时任务,这会使更新UI界面操作阻塞在队列中得不到处理,使程序失去响应;③耗时操作应该放到独立的任务线程中,通过SwingWorker启动。
    好巧不巧,前面两点我好像都犯了,我想在子线程中更新主线程UI,所以将组件传到子线程中,视图在子线程中更新UI;然后为了获取到让主线程等待所有子线程运行完毕,我选择将主线程阻塞。
    很巧,我就碰到了这个问题,上面博客中接下来就有一句话,上面两点会导致一个矛盾: 耗时操作必须在工作线程中执行,否则会使UI界面产生停住的现象,而工作线程中更新UI又会出现非线程安全问题。
    而为了解决这个矛盾,就需要使用SwingUtilities类中的invokeLater()和invokeAndWait()这两种方法,通过这两种方法可以将一个可执行对象(Runnable)实例追加到EDT的可执行事件队列中。
3.invokeLater()和invokeAndWait()两种方法的区别
    invokeLater()是异步的,也就是EDT将时间放到事件队列中就返回;invokeAndWait()是同步的,即在EDT将时间放到队列中,等待Runnable执行完毕才返回,也就是这种方法会阻塞调用该方法的线程,直到对应的Run方法执行完成,所以调用这种方法也会出现前面的UI停止现象。
    那么这里引申出来一个问题就是invokeLater方法将时间放到队列中,这个事件什么时候会执行,应该是在队列中事件执行到这个事件的时候就会执行,可是这样好像还是达不到我想要的结果,我需要的是子线程与主线程通信,然后主线程更新UI。
4.invokeLater()和invokeAndWait()两种方法的使用
    接下来这个博客侧重于使用,看起来能解决我的疑惑。
     Swing是事件驱动的,所以在回调函数中更新可见的GUI是很常见的,而从事件派发线程外的线程更新Swing组件是不正常的,但有时却的确需要这么做,我上面就是这样。
    因此就需要使用的invokeLate和invoteAndWait方法,他们会将时间派发线程上的可运行对象排队,当对象排在队首时就会调用run方法,其效果是允许时间派发线程,调用另一个线程的任意一个代码块。
    只有事件派发线程能更新组件。这句话跟上面那两点一个意思,不能在子线程更新主线程UI。
    ①invokeLater:后面的参数与线程对象可以并行异步执行;一般用于在线程中修改swing组件的外观,所以我上面应该可以在主线程中使用这个函数来执行线程,并且在这个线程的耗时任务完成之后,紧接着修改GUI;这样可以使不见得外观与事件处理可以协调,在某些情况下,可以使部件立即显示,而处理耗时代码放到后台运行。
    ②invokeAndWait:这里在前面介绍过,就是会将当前线程阻塞,是一个同步的过程,一般不会被使用
5.invokeLater()的实例
    invokeAndWait()很少使用,这里就举例invokeLater的实例,主要参考博客:
    我这一块儿使用的代码如下:
threads[i] = new Thread(new Runnable() {

                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            System.out.println("create Thread");
                            ScriptProcess scriptProcess = new ScriptProcess(strXMLContent, strXMLName);
                            
                            if(flagLock) {
                                flagLock = false;
                                int nStatus = 0;
                                if(scriptProcess.GetResult()) {
                                    textAreaLogInfo.append(strXMLName + "验证通过\n");
                                    if(tableTaskItem.getValueAt(nIndex, 1).equals("未通过")) {
                                        nPassTestItem++;
                                        nFailTestItem--;
                                    }
                                    else if(tableTaskItem.getValueAt(nIndex, 1).equals("未验证")) {
                                        nPassTestItem++;
                                        nUnCheckTestItem--;
                                    }
                                    tableTaskItem.setValueAt("已通过", nIndex, 1);
                                    //nPassTestItem++;
                                    nStatus = 2;
                                    //listStatus.add(2);
                                }
                                else {
                                    textAreaLogInfo.append(strXMLName + "验证失败\n");
                                    if(tableTaskItem.getValueAt(nIndex, 1).equals("已通过")) {
                                        nPassTestItem--;
                                        nFailTestItem++;
                                    }
                                    else if(tableTaskItem.getValueAt(nIndex, 1).equals("未验证")) {
                                        nFailTestItem++;
                                        nUnCheckTestItem--;
                                    }
                                    tableTaskItem.setValueAt("未通过", nIndex, 1);
                                    //nFailTestItem++;
                                    nStatus = 1;
                                }    
                                nCheckItem++;
                                //progressBarBatchExecute.setValue((nCheckItem)*MAX_PROGRESS/listIndex.size());
                                //System.out.println("Progress Value:" + (nCheckItem)*MAX_PROGRESS/listIndex.size());
                                Connection conn = ConnectToSysDB.getConn();
                                int nTaskIndex = comboBoxTestTask.getSelectedIndex();
                                int nTaskID = Integer.parseInt((String) listIDOfTestTask.get(nTaskIndex).get("ID"));    
                                //int nIndex = listIndex.get(i);
                                int nItemID = Integer.parseInt(listXMLInfo.get(nIndex).get("ID"));                                
                                ConnectToSysDB.updateTestStatusOfItemInfo(conn, nItemID, nTaskID, nStatus);
                                if(nCheckItem == listIndex.size()) {
                                    if(nPassTestItem == nSumTestItem) {
                                        lblTestTaskStatusInfo.setText("已通过");
                                        //更新一下数据库
                                        Boolean flagTmp = ConnectToSysDB.updateCheckTestTaskSysInfo(conn, nTaskID, 2);
                                        if(!flagTmp) {
                                            JOptionPane.showMessageDialog(null, "修改测试任务系统信息表的测试任务验证状态成功");
                                            return;
                                        }
                                    }
                                    else {
                                        lblTestTaskStatusInfo.setText("未通过");
                                        Boolean flagTmp = ConnectToSysDB.updateCheckTestTaskSysInfo(conn, nTaskID, 1);
                                        if(!flagTmp) {
                                            JOptionPane.showMessageDialog(null, "修改测试任务系统信息表的测试任务验证状态失败");
                                            return;
                                        }
                                    }
                                    //JOptionPane.showMessageDialog(null, "执行完毕");
                                }                            
                                ConnectToSysDB.closeConn(conn);
                                /*nSerial++;
                                System.out.println("create invokeLater");                                        
                                progressBarBatchExecute.setValue((nSerial)*MAX_PROGRESS/listIndex.size());
                                System.out.println("Progress Value:" + (nSerial)*MAX_PROGRESS/listIndex.size());
                                if(nSerial == listIndex.size()) {
                                    JOptionPane.showMessageDialog(null, "执行完毕");
                                }*/
                                
                                flagLock = true;
                            }
                            SwingUtilities.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    // TODO Auto-generated method stub
                                    nSerial++;
                                    progressBarBatchExecute.setValue((nSerial)*MAX_PROGRESS/listIndex.size());                                    
                                    System.out.println("Progress Value:" + (nSerial)*MAX_PROGRESS/listIndex.size());
//                                    nSerial++;
//                                    System.out.println("create invokeLater");                                        
//                                    progressBarBatchExecute.setValue((nSerial)*MAX_PROGRESS/listIndex.size());
//                                    System.out.println("Progress Value:" + (nSerial)*MAX_PROGRESS/listIndex.size());
//                                    if(nSerial == listIndex.size()) {
//                                        JOptionPane.showMessageDialog(null, "执行完毕");
//                                    }
                                }
                                
                            });
                            
                        }
                        
                    });
                    threads[i].start();

    在创建子线程并且进行了主要的操作之后,再调用invokeLater来更新主线程的UI。
    这里需要注意几点:①主线程是不能被阻塞的,我之前将更新数据库的操作放到主线程中,然后再阻塞主线程,试图让主线程可以在子线程执行完毕之后再进行数据库操作,这样就违反了第二条里面罗列的几点了,另外事务调度队列中第一个事务就是主线程,当你把它阻塞的时候,invokeLater()中的run方法不会被执行,所以索性就把主线程后面仍需要做的数据库操作放到子线程中。②不过我后来发现,在子线程中其实也可以同样进行UI操作,程序并不会报错,这一点我在将执行数据库操作之前改变进度条,也能达到我预期的效果(数据库操作上的注释去掉也是可以以我的要求改变进度条)③访问相同变量的时候记得上锁。
6.主线程子线程与事件派发线程。
        Swing的线程有两类,一种是事件派发线程,实际上只有唯一的一条线程,另一类就是一般的线程,可以有无数条线程。我感觉这个事件派发线程就是主线程吧,然后一般的线程就是子线程,最好不要在子线程更新UI,也就是说不要在事件派发线程以外的线程去更新UI,所以就引入了invokeLater方法,而子线程就是工作线程做一些比较耗时的操作。
结论:
①主线程一定不要轻易阻塞,主线程后面最好就别做操作了
②子线程改变UI,最好使用invokeLater,虽然不用好像也可以达到预期目标,虽然我觉得我加个锁也可以实现并发执行。。
③使用invokeLater将它的run添加到事件调度队列中,但是这个事件调度队列第一个是主线程,主线程如果被阻塞,这个事件调度队列后面的事件自然也不会执行。
待加强/学习资料:
①线程池的使用
②工作线程与EDT线程的交互
③主线程等待子线程结束的多种方法:
④开启一个线程的三种方式:
⑤线程锁(我上面用标志好像笨比了):
⑥java swing组件:
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑咖啡不加糖丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值