java线程安全:Java开发中的线程安全选择和Swing

Swing API设计目标是强大、灵活和易用特别地我们希望能让员们方便地建立新Swing组件不论是从头开始还是通过扩展我们所提供些组件   出于这个目我们不要求Swing组件支持多线程访问相反我们向组件发送请求并在单线程中执行请求   本文讨论线程和Swing组件不仅是为了帮助你以线程安全方式使用Swing API而且解释了我们为什么会选择现在这样线程方案   本文包括以下内容:    单线程规则:Swing线程在同时刻仅能被个线程所访问般来说这个线程是事件派发线程(event-dispatching thread)    规则例外:有些操作保证是线程安全    事件分发:如果你需要从事件处理(event-handling)或绘制代码以外地方访问UI那么你可以使用SwingUtilities类invokeLater或invokeAndWait思路方法    创建线程:如果你需要创建个线程——比如用来处理些耗费大量计算能力或受I/O能力限制工作——你可以使用个线程工具类如SwingWorker或Timer    为什么我们这样实现Swing:我们将用些有关Swing线程安全背景资料来结束这篇文章   Swing规则是:   旦Swing组件被具现化(realized)所有可能影响或依赖于组件状态代码都应该在事件派发线程中执行   这个规则可能听起来有点吓人但对许多简单来说你用不着为线程问题操心在我们深入如何撰写Swing代码的前让我们先来定义两个术语:具现化(realized)和事件派发线程(event-dispatching thread)   具现化意思是组建pa思路方法已经或可能会被个作为顶级窗口Swing组件当以下思路方法时将被具现化:Visible(true)、show或(可能令你惊奇)pack个窗口被具现化它包含所有组件都被具现化个具现化个组件思路方法是将它放入到个已经具现化容器中稍后你会看到些对组件具现化例子   事件派发线程是执行绘制和事件处理线程例如pa和actionPerformed思路方法会自动在事件派发线程中执行个将代码放到事件派发线程中执行思路方法是使用SwingUtilities类invokeLater思路方法   所有可能影响个已具现化Swing组件代码都必须在事件派发线程中执行但这个规则有些例外:   有些思路方法是线程安全:在Swing API文档中线程安全思路方法用以下文字标记:   This method is thread safe, although most Swing methods are not.   (这个思路方法是线程安全尽管大多数Swing思路方法都不是) 个应用GUI常常可以在主线程中构建和显示:下面典型代码是安全只要没有(Swing或其他)组件被具现化: public MyApplication {  public void (String args)  {   JFrame f = JFrame("Labels"); // 在这里将各组件    // 加入到主框架……    f.pack;    f.show;    // 不要再做任何GUI工作……   } }   上面所示代码全部在“”线程中运行对f.pack使得JFrame以下组件都被具现化这意味着f.show是不安全且应该在事件派发线程中执行尽管如此只要还没有个看得到GUIJFrame或它里面组件就几乎不可能在f.show返回前收到个pa在f.show的后不再有任何GUI代码于是所有GUI工作都从主线程转到了事件派发线程因此前面所讨论代码实际上是线程安全   个appletGUI可以在init思路方法中构造和显示:现有浏览器都不会在个appletinit和start思路方法被前绘制它因而个appletinit思路方法中构造GUI是安全只要你不对applet中对象showVisible(true)思路方法   要顺便如果applet中使用了Swing组件就必须实现为JApplet子类并且组件应该添加到JApplet内容窗格(content pane)中而不要直接添加到JApplet对任何applet你都不应该在init或start思路方法中执行费时化操作;而应该启动个线程来执行费时任务   下述JComponent思路方法是安全可以从任何线程:repa、revalidate、和invalidaterepa和revalidate思路方法为事件派发线程对请求排队并分别pa和validate思路方法invalidate思路方法只在需要确认时标记个组件和它所有直接祖先   监听者列表可以由任何线程修改:addListenerTypeListener和removeListenerTypeListener思路方法总是安全对监听者列表添加/删除操作不会对进行中事件派发有任何影响   注意:revalidate和旧validate思路方法的间重要区别是revalidate会缓存Cache请求并组合成次validate这和repa缓存Cache并组合绘制请求类似   大多数化后GUI工作自然地发生在事件派发线程旦GUI成为可见大多数都是由事件驱动如按钮动作或鼠标点击这些总是在事件派发线程中处理   不过总有些需要在GUI成为可见后执行些非事件驱动GUI工作比如:   在成为可用前需要进行长时间化操作:这类通常应该在化期间就显示出GUI然后更新或改变GUI化过程不应该在事件派发线程中进行;否则重绘组件和事件派发会停止尽管如此化的后GUI更新/改变还是应该在事件派发线程中进行理由是线程安全   必须响应非AWT事件来更新GUI:例如想象个服务器从可能运行在其他机器上得到请求这些请求可能在任何时刻到达并且会引起在些可能未知线程中对服务器思路方法这个思路方法怎样更新GUI呢?在事件派发线程中执行GUI更新代码   SwingUtilities类提供了两个思路方法来帮助你在事件派发线程中执行代码:    invokeLater:要求在事件派发线程中执行某些代码这个思路方法会立即返回不会等待代码执行完毕    invokeAndWait:行为和invokeLater类似除了这个思路方法会等待代码执行完毕般地你可以用invokeLater来代替这个思路方法   下面是些使用这几个API例子请同时参阅The Java Tutorial“BINGO example”尤其是以下几个类:CardWindow、ControlPane、Player和OverallStatusPane 使用invokeLater思路方法   你可以从任何线程invokeLater思路方法以请求事件派发线程运行特定代码你必须把要运行代码放到个Runnable对象run思路方法中并将此Runnable对象设为invokeLater参数invokeLater思路方法会立即返回不等待事件派发线程执行指定代码这是个使用invokeLater思路方法例子: Runnable doWorkRunnable = Runnable {  public void run  {   doWork;   } }; SwingUtilities.invokeLater(doWorkRunnable);   使用invokeAndWait思路方法   invokeAndWait思路方法和invokeLater思路方法很相似除了invokeAndWait思路方法会等事件派发线程执行了指定代码才返回在可能情况下你应该尽量用invokeLater来代替invokeAndWait如果你真要使用invokeAndWait请确保invokeAndWait线程不会在期间持有任何其他线程可能需要 这是个使用invokeAndWait例子: void showHelloThereDialog throws Exception {  Runnable showModalDialog = Runnable  {   public void run   {    JOptionPane.showMessageDialog( myMainFrame, "Hello There");    }   };  SwingUtilities.invokeAndWait (showModalDialog); }   类似地假设个线程需要对GUI状态进行存取比如文本域内容代码可能类似这样: void prTextField   throws Exception {    final String myStrings = String[2];    Runnable getTextFieldText = Runnable {     public void run {      myStrings[0] = textField0.getText;      myStrings[1] = textField1.getText;     }    };    SwingUtilities.invokeAndWait (getTextFieldText);    .out.prln(myStrings[0] + " " + myStrings[1]);}   如果你能避免使用线程最好这样做线程可能难于使用并使得debug更困难般来说对于严格意义下GUI工作线程是不必要比如对组件属性更新   不管如何说有时候线程是必要下列情况是使用线程些典型情况:   执行项费时任务而不必将事件派发线程锁定例子包括执行大量计算情况会导致大量类被装载情况(如化)和为网络或磁盘I/O而阻塞情况   重复地执行项操作通常在两次操作间间隔个预定时间周期   要等待来自客户消息   你可以使用两个类来帮助你实现线程:    SwingWorker:创建个后台线程来执行费时操作    Timer:创建个线程来执行或多次执行某些代码在两次执行间间隔用户定义延迟 使用SwingWorker类   SwingWorker类在SwingWorker.java中实现这个类并不包含在Java任何发行版中所以你必须单独下载它   SwingWorker类做了所有实现个后台线程所需肮脏工作虽然许多都不需要后台线程后台线程在执行费时操作时仍然是很有用它能提高性能观感 SwingWorker's get method. Here's an example of using SwingWorker:   要使用SwingWorker类你首先要实现它个子类在子类中你必须实现construct思路方法还包含你长时间操作当你例子化SwingWorker子类时SwingWorker创建个线程但并不启动它你要SwingWorker对象start思路方法来启动线程然后start思路方法会construct思路方法当你需要construct思路方法返回对象时可以SwingWorker类get思路方法这是个使用SwingWorker类例子: ...// 在思路方法中: final SwingWorker worker = SwingWorker {  public Object construct {    expensiveDialogComponent;   } }; worker.start; ... // 在动作事件处理思路方法中: JOptionPane.showMessageDialog (f, worker.get);   当思路方法start思路方法SwingWorker启动个新线程来例子化ExpensiveDialogComponent思路方法还构造了由个窗口和个按钮组成GUI   当用户点击按钮将阻塞如果必要阻塞到ExpensiveDialogComponent创建完成然后显示个包含ExpensiveDialogComponent模式对话框你可以在MyApplication.java找到整个   使用Timer类   Timer类通过个ActionListener来执行或多次执行项操作你创建定时器时候可以指定操作执行频率并且你可以指定定时器动作事件监听者(action listener)启动定时器后动作监听者actionPerformed思路方法会被(多次)来执行操作   定时器动作监听者(action listener)定义actionPerformed思路方法将在事件派发线程中这意味着你不必在其中使用invokeLater思路方法   这是个使用Timer类来实现动画循环例子: public AnimatorApplicationTimer  extends JFrame implements ActionListener {   ...//在这里定义例子变量   Timer timer;   public AnimatorApplicationTimer(...) {    ... // 创建个定时器来    // 来此对象action handler    timer = Timer(delay, this);    timer.InitialDelay(0);    timer.Coalesce(true);    ...   }   public void startAnimation {     (frozen) {     // 什么都不做应用户要求     // 停止变换图像    } {     // 启动(或重启动)动画!     timer.start;    }   }   public void stopAnimation {    // 停止动画线程    timer.stop;   }   public void actionPerformed (ActionEvent e)   {    // 进到下帧动画    frameNumber;    // 显示    repa;   }   ... } 在个线程中执行所有用户界面代码有这样些优点:   组件开发者不必对线程编程有深入理解:像ViewPo和Trestle这类工具包中所有组件都必须完全支持多线程访问使得扩展非常困难尤其对不精通线程编程开发者来说最近些工具包如SubArctic和IFC都采用和Swing类似设计   事件以可预知次序派发:invokeLater排队runnable对象从鼠标和键盘事件、定时器事件、绘制请求个队列派发些组件完全支持多线程访问工具包中组件改变被变化无常线程调度穿插到事件处理过程中这使得全面测试变得困难甚至不可能   更低代价:尝试小心锁住临界区工具包要花费实足时间和空间在锁管理上每当工具包中某个可能在客户代码中实现思路方法时(如public类中任何public和protected思路方法)工具包都要保存它状态并释放所有锁以便客户代码能在必要时获得锁当控制权交回到工具包工具包又必须重新抓住它锁并恢复状态所有应用都不得不负担这代价即使大多数应用并不需要对GUI并发访问   这是SubArctic Java Toolkit作者对在工具包中支持多线程访问问题描述:   我们基本信条是当设计和建造多线程应用尤其是那些包括GUI组件应用必须保证极端小心线程使用可能会很有欺骗性在许多情况下它们表现得能够极好简化编成使得设计“专注于单任务简单自治实体”成为可能些情况下它们确简化了设计和编码然而在几乎所有情况下它们都使得调试、测试和维护困难大大增加甚至成为不可能无论大多数员所受训练、他们经验和实战还是我们用来帮助自己工具都不是能够用来对付非决定论例如全面测试(这总是困难)在bug依赖于时间时是几乎不可能尤其对于Java来说要运行在许多区别类型机器操作系统平台上并且每个都必须在抢先和非抢先式调度下都能正常工作   由于这些固有困难我们力劝你 3思是否绝对有使用线程必要尽管如此有些情况下使用线程是必要(或者是被其他软件Software包强加)所以subArctic提供了个线程安全访问机制本章讨论了这机制和怎样在个独立线程中安全地操作交互树   他们所说线程安全机制非常类似于SwingUtilities类提供invokeLater和invokeAndWait思路方法
Tags:  线程安全的类 servlet线程安全 线程安全 java线程安全

延伸阅读

最新评论

发表评论