【Swing入门教程】一步一步做Netbeans(5):Swing的线程管理及如何写健壮的安全的正确的Swing程序

    技术2022-05-20  48

         又过了12点。N天前在china-pub订的两本书经过漫长的等待今天终于到手了:《 Core Java, Vol. 2: Advanced Features 》和《 Filthy Rich Clients 》。这年头,快递都涨价了速度倒没见快哪去。最近有点急躁,没事,每月都有那么几天,哦,别误会,我是说男性生理周期。

         至本教程,我们一直都只是做些界面或组件,没过多考虑程序结构和逻辑的问题。事先也声明了,只是做界面,不管逻辑。逻辑可以不做,但是程序的结构可不能乱来,这样会误人子弟。回到教程4,其中有一部是初始化文件树,我们把这段放到界面初始化完并显示后再做,看看效果怎样;这样的话得改一下,给JTree声明了DefaultTreeModel,并增加了一个自定义的状态栏StatusBar(代码放在后面):

    private DefaultTreeModel treeModel=new DefaultTreeModel(null); private StatusBar statusBar=new StatusBar();

           JTree初始化部分也修改一下:

    JTree tree=new JTree(treeModel); tree.setCellRenderer(new TreeRenderer()); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setRootVisible(true); leftTabbedPane.add("文件", new JScrollPane(tree));

          initTree()也跟着修改一下,addDir()方法不变:

    private void initTree(){ DefaultMutableTreeNode top = new DefaultMutableTreeNode(null); File dir=new File("C:/Users/monitor/Documents/NetBeansProjects/"); addDir(top,dir); treeModel.setRoot(top); }

         我们想让主界面出来后再初始化文件树:

    public NetbeansUI(){ initComponents(); setVisible(true); initData(); } private void initData(){ statusBar.barShow(); initTree(); statusBar.barHide(); }

         运行后会出现什么效果呢?预期是界面出现后出现进度条初始化文件树,完成后进度条隐藏。而实质上是主界面出现后卡了大概3秒钟后文件树出现,却没发现进度条。刚学Swing时我也是大骂这JProgressBar怎么不好用啊。要知道这为什么,你就需要了解一下Swing的单线程模式了。

         一:EDT

         无论什么时候,运行一个Swing应用程序都会自动创建三个线程。一是主线程,它运行着应用程序的主方法。二是工具包线程,负责捕获系统事件,它不会运行应用程序的代码,捕获的事件会发送到第三个线程EDT(Event Dispatch Thread,事件派发线程)。

         EDT与Swing的交互算是最密切的了。它负责把事件派发到适当的组件并调用它们的绘制方法,正是由于此,我们才会看到一个变化着的界面。可以这样说,AWT和Swing中的任何事情的进行都或多或少和EDT有关。

         单线程,队列,你有点眉目了吗。当我们再EDT中执行一个耗时较长的操作时,界面就刷新不过来了,因为刷新部分还在排队。我们以上的程序正是由于此而造成卡3秒的。以前看Swing组件的javadoc时都会看到这样的一句话:Swing 不是线程安全的。有关更多信息,请参阅 Swing's Threading Policy 。那时我还也真是莫名奇妙。

         二:SwingUtilities

         知道问题所在就好办了,既然耗时操作不能放到EDT中,那就放到一个新的线程中去吧:

    private void initData(){ new Thread(new Runnable() { public void run() { statusBar.barShow(); initTree(); statusBar.barHide(); } }).start(); }

          运行时没出现什么问题,进度条也能显示,看似一切都正常。不过它违反了Swing的单线程规则:修改组件的状态要在EDT中进行。测试时可能没引起任何问题,但随时都有可能出现死锁。这个我是深有体会,自己开发的编辑器逻辑是没什么问题,就是跑着跑着就出现一些莫名其妙的异常来,并且根据异常信息看似和自己写的代码半毛子的关系都没有。

          尝试用Swing的工具类SwingUtilities中的invokeLater(),它可以用于EDT中发布一个新任务。一般Netbeans自动生成的代码中都会用该方法初始化Swing组件。把initData()改成这样:

    private void initData(){ new Thread(new Runnable() { public void run() { SwingUtilities.invokeLater(new Runnable(){ public void run(){ statusBar.barShow(); initTree(); statusBar.barHide(); } }); } }).start(); }

    三:SwingWorker

          SwingUtilities是很有用,不过它要创建不少匿名Runnable类,但要做的事情多且繁琐时SwingUtilities写的代码就难于阅读和维护了。这时不妨考虑下SwingWorker,这可是Java SE 6中新加的哦。它可以在一个后台线程中运行一个特定的任务,在EDT上发布任务进行的中间结果和最终结果,关于SwingWorker的具体用法请查看javadoc吧,我困得不行了。下面给出构造文件树的SwingWorker的实现:

    package com.monitor1394.netbeans.ui; import com.monitor1394.netbeans.component.StatusBar; import java.io.File; import java.util.List; import javax.swing.SwingWorker; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; /** * 构造文件树 * * @author monitor * Created on 2011-2-28, 23:49:40 */ public class DirLoadingWorker extends SwingWorker<DefaultMutableTreeNode,String>{ private StatusBar bar; private DefaultTreeModel treeModel; public DirLoadingWorker(StatusBar bar,DefaultTreeModel model){ this.bar=bar; treeModel=model; bar.barShow(); } /** 任务做完后将节点赋给treeModel并隐藏进度条 */ @Override protected void done(){ try { treeModel.setRoot(get()); bar.barHide(); bar.setBarInfo("项目扫描完毕"); } catch (Exception ex) { } } /** 任务进行中输出显示信息 */ @Override protected void process(List<String> messages){ for(String message:messages){ bar.setBarMsg(message); } } /** 任务后台处理 通过文件夹构造一个根节点 */ @Override protected DefaultMutableTreeNode doInBackground(){ DefaultMutableTreeNode top = new DefaultMutableTreeNode(null); File dir=new File("C:/Users/monitor/Documents/NetBeansProjects/"); addDir(top,dir); return top; } /** 递归将文件(夹)转为JTree的节点 */ private DefaultMutableTreeNode addDir(DefaultMutableTreeNode top,File file){ if(file.isDirectory()){ for(File f:file.listFiles()){ DefaultMutableTreeNode t = new DefaultMutableTreeNode(f.getName()); publish("Loading "+f.getName()); if(f.isDirectory()) top.add(addDir(t,f)); else top.add(t); } }else{ top.add(new DefaultMutableTreeNode(file.getName())); } return top; } }

          要运行它,将NetbeansUI类中的38行改为:

    new DirLoadingWorker(statusBar,treeModel).execute();

    搞定,哦,还不能收工。附上StatusBar类的代码:

    package com.monitor1394.netbeans.component; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JSeparator; /** * 状态栏 * * @author monitor * Created on 2011-2-28, 23:30:50 */ public class StatusBar extends JPanel{ public StatusBar(){ initComponents(); } private void initComponents(){ JPanel part1,part2; setLayout(new java.awt.GridLayout()); part1=new JPanel(); part1.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT)); tipLabel=new JLabel(""); part1.add(tipLabel); add(part1); part2=new JPanel(); part2.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); msgLabel=new JLabel("MirrorTool(run)"); part2.add(msgLabel); bar=new JProgressBar(); part2.add(bar); separator1=new JSeparator(); separator1.setOrientation(javax.swing.SwingConstants.VERTICAL); part2.add(separator1); // closeButton=new JButton(createImageIcon("images/close.jpg")); // part2.add(closeButton); xLabel=new JLabel("0"); part2.add(xLabel); yLabel=new JLabel("0"); part2.add(yLabel); endLabel=new JLabel("INS"); part2.add(endLabel); add(part2); barHide(); } public void barHide(){ msgLabel.setText(null); bar.setVisible(false); } public void barShow(){ bar.setVisible(true); bar.setIndeterminate(true); } public void setBarMsg(String msg){ msgLabel.setText(msg); } public void setBarInfo(String msg){ tipLabel.setText(msg); } public void setBarIndeterminate(boolean flag){ bar.setIndeterminate(flag); } public void setBarValue(int value){ bar.setValue(value); } public void setBarMaximum(int max){ bar.setMaximum(max); } private static ImageIcon createImageIcon(String path) { java.net.URL imgURL = StatusBar.class.getResource(path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } private JProgressBar bar; private JLabel tipLabel; private JLabel msgLabel; private JLabel endLabel; private JLabel xLabel; private JLabel yLabel; private JSeparator separator1; private JButton closeButton; }

     


    最新回复(0)