﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Vincent-随笔分类-j2se</title><link>http://www.blogjava.net/lijiajia418/category/14362.html</link><description>Vicent's blog</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:41:40 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:41:40 GMT</pubDate><ttl>60</ttl><item><title>Java 技术: 使您轻松地进行多线程应用程序编程</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65605.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:53:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65605.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65605.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65605.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65605.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65605.html</trackback:ping><description><![CDATA[产者-消费者方案是多线程应用程序开发中最常用的构造之一 ― 因此困难也在于此。因为在一个应用程序中可以多次重复生产者-消费者行为，其代码也可以如此。软件开发人员 Ze'ev Bubis 和 Saffi Hartal 创建了 <code><font face="Courier" size="2">Consumer</font></code> 类，该类通过在一些多线程应用程序中促进代码重用以及简化代码调试和维护来解决这个问题。请通过单击本文顶部或底部的 <b>讨论</b>来参与本文的 <a href="javascript:void forumWindow()"><font color="#996699">论坛</font></a>，与作者和其他读者分享您的想法。 <!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES--><p>多线程应用程序通常利用生产者-消费者编程方案，其中由生产者线程创建重复性作业，将其传递给作业队列，然后由消费者线程处理作业。虽然这种编程方法很有用，但是它通常导致重复的代码，这对于调试和维护可能是真正的问题。</p><p>为了解决这个问题并促进代码重用，我们创建了 <code><font face="Courier" size="2">Consumer</font></code> 类。 <code><font face="Courier" size="2">Consumer</font></code> 类包含所有用于作业队列和消费者线程的代码，以及使这两者能够结合在一起的逻辑。这使我们可以专注于业务逻辑 ― 关于应该如何处理作业的细节 ― 而不是专注于编写大量冗余的代码。同时，它还使得调试多线程应用程序的任务变得更为容易。 </p><p>在本文中，我们将简单观察一下多线程应用程序开发中公共线程用法，同时，解释一下生产者-消费者编程方案，并研究一个实际的示例来向您演示 <code><font face="Courier" size="2">Consumer</font></code> 类是如何工作的。请注意，对于多线程应用程序开发或消费者-生产者方案，本文不作深入介绍；有关那些主题，请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#resources"><font color="#996699">参考资料</font></a>获取文章的清单。 </p><p><a name="1"><span class="atitle"><strong><font size="4">多线程基础知识</font></strong></span></a></p><p>多线程是一种使应用程序能同时处理多个操作的编程技术。通常有两种不同类型的多线程操作使用多个线程：</p><ul><li><b>适时事件</b>，当作业必须在特定的时间或在特定的间隔内调度执行时 <br /></li><li><b>后台处理</b>，当后台事件必须与当前执行流并行处理或执行时 </li></ul><p>适时事件的示例包括程序提醒、超时事件以及诸如轮询和刷新之类的重复性操作。后台处理的示例包括等待发送的包或等待处理的已接收的消息。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="2"><span class="atitle"><strong><font size="4">生产者-消费者关系</font></strong></span></a></p><p>生产者-消费者方案很适合于后台处理类别的情况。这些情况通常围绕一个作业“生产者”方和一个作业“消费者”方。当然，关于作业并行执行还有其它考虑事项。在大多数情况下，对于使用同一资源的作业，应以“先来先服务”的方式按顺序处理，这可以通过使用单线程的消费者轻松实现。通过使用这种方法，我们使用单个线程来访问单个资源，而不是用多个线程来访问单个资源。</p><p>要启用标准消费者，当作业到来时创建一个作业队列来存储所有作业。生产者线程通过将新对象添加到消费者队列来交付这个要处理的新对象。然后消费者线程从队列取出每个对象，并依次处理。当队列为空时，消费者进入休眠。当新的对象添加到空队列时，消费者会醒来并处理该对象。因为大多数应用程序喜欢顺序处理方式，所以消费者通常是单线程的。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="3"><span class="atitle"><strong><font size="4">问题：代码重复</font></strong></span></a></p><p>因为生产者-消费者方案很常用，所以在构建应用程序时它可能会出现几次，这导致了代码重复。我们认识到，这显示了在应用程序开发过程期间多次使用了生产者-消费者方案的问题。</p><p>当第一次需要生产者-消费者行为时，通过编写一个采用一个线程和一个队列的类来实现该行为。当第二次需要这种行为时，我们着手从头开始实现它，但是接着认识到以前已经做过这件事了。我们复制了代码并修改了处理对象的方式。当第三次在该应用程序中实现生产者-消费者行为时，很明显我们复制了太多代码。我们决定，需要一个适用的 <code><font face="Courier" size="2">Consumer</font></code> 类，它将处理我们所有的生产者-消费者方案。 </p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="4"><span class="atitle"><strong><font size="4">我们的解决方案：Consumer 类</font></strong></span></a></p><p>我们创建 <code><font face="Courier" size="2">Consumer</font></code> 类的目的是：在我们的应用程序中，消除这种代码重复 ― 为每个生产者-消费者实例编写一个新作业队列和消费者线程来解决这个问题。有了适当的 <code><font face="Courier" size="2">Consumer</font></code> 类，我们所必须做的只是编写专门用于作业处理（业务逻辑）的代码。这使得我们的代码更清晰、更易于维护以及更改起来更灵活。 </p><p>我们对 <code><font face="Courier" size="2">Consumer</font></code> 类有如下需求： </p><ul><li><b>重用</b>：我们希望这个类包括所有东西。一个线程、一个队列以及使这两者结合在一起的所有逻辑。这将使我们只须编写队列中“消费”特定作业的代码。（因而，例如，程序员使用 <code><font face="Courier" size="2">Consumer</font></code> 类时，将重载 <code><font face="Courier" size="2">onConsume(ObjectjobToBeConsumed)</font></code> 方法。） <br /></li><li><b>队列选项</b>：我们希望能够设置将由 <code><font face="Courier" size="2">Consumer</font></code> 对象使用的队列实现。但是，这意味着我们必须确保队列是线程安全的或使用一个不会与消费操作冲突的单线程生产者。无论使用哪种方法，都必须将队列设计成允许不同的进程能访问其方法。 <br /></li><li><b>Consumer 线程优先级</b>：我们希望能够设置 <code><font face="Courier" size="2">Consumer</font></code> 线程运行的优先级。 <br /></li><li><b>Consumer 线程命名</b>：线程拥有一个有意义的名称会比较方便，当然这的确有助于调试。例如，如果您向 Java 虚拟机发送了一个信号，它将生成一个完整的线程转储 ― 所有线程及其相应堆栈跟踪的快照。要在 Windows 平台上生成这个线程转储，您必须在 Java 程序运行的窗口中按下键序列 <code><font face="Courier" size="2">&lt;ctrl&gt;&lt;break&gt;</font></code> ，或者单击窗口上的“关闭”按钮。有关如何使用完整的线程转储来诊断 Java 软件问题的更多信息，请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#resources"><font color="#996699">参考资料</font></a>。 </li></ul><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="5"><span class="atitle"><strong><font size="4">类代码</font></strong></span></a></p><p>在 <code><font face="Courier" size="2">getThread()</font></code> 方法中，我们使用“惰性创建”来创建 <code><font face="Courier" size="2">Consumer</font></code> 的线程，如清单 1 所示： </p><br /><a name="code1"><b>清单 1. 创建 Consumer 的线程</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">     /**
       * Lazy creation of the Consumer's thread.
       *
       * @return  the Consumer's thread
       */
      private Thread getThread()
      {
         if (_thread==null)
         {
            _thread = new Thread()
            {
               public void run()
               {
                  Consumer.this.run();
               }
            };
         }
         return _thread;
</font></code></pre></td></tr></tbody></table><br /><p>该线程的 <code><font face="Courier" size="2">run()</font></code> 方法运行 <code><font face="Courier" size="2">Consumer</font></code> 的 <code><font face="Courier" size="2">run()</font></code> 方法，它是主消费者循环，如清单 2 所示： </p><br /><a name="code2"><b>清单 2. run() 方法是主 Consumer 循环</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">     /**
       *  Main Consumer's thread method.
       */
      private void run()
      {
         while (!_isTerminated)
         {
            // job handling loop
      while (true)
            {
               Object o;
               synchronized (_queue)
               {
                  if (_queue.isEmpty())
          break;
                  o = _queue.remove();
               }
               if (o == null)
          break;
               onConsume(o);
            }

            // if we are not terminated and the queue is still empty
            // then wait until new jobs arrive.

            synchronized(_waitForJobsMonitor)
            {
               if (_isTerminated)
          break;
               if(_queue.isEmpty())
               {
        try
                  {
                     _waitForJobsMonitor.wait();
                  }
                  catch (InterruptedException ex)
                  {
                  }
               }
            }
         }
}// run()
</font></code></pre></td></tr></tbody></table><br /><p>基本上， <code><font face="Courier" size="2">Consumer</font></code> 的线程一直运行，直到队列中不再有等待的作业为止。然后它进入休眠，只在第一次调用 <code><font face="Courier" size="2">add(Object)</font></code> 时醒来，该方法向队列添加一个新作业并“踢”醒该线程。 </p><p>使用 <code><font face="Courier" size="2">wait()</font></code> 和 <code><font face="Courier" size="2">notify()</font></code> 机制来完成“睡眠”和“踢”。实际的消费者工作由 <code><font face="Courier" size="2">OnConsume(Object)</font></code> 方法处理，如清单 3 所示： </p><br /><a name="code3"><b>清单 3. 唤醒和通知 Consumer</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">     /**
      * Add an object to the Consumer.
      * This is the entry point for the producer.
      * After the item is added, the Consumer's thread
      * will be notified.
      *
      * @param  the object to be 'consumed' by this consumer
      */
      public void add(Object o)
      {
         _queue.add(o);
         kickThread();
      }

      /**
       * Wake up the thread (without adding new stuff to consume)
       *
       */
      public void kickThread()
      {
         if (!this._thread.isInterrupted())
         {
            synchronized(_waitForJobsMonitor)
            {
               _waitForJobsMonitor.notify();
            }
         }
      }
</font></code></pre></td></tr></tbody></table><br /><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><font face="Lucida Console"><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></font></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><font face="Lucida Console"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /></font><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><font face="Lucida Console"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></font></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="6"><span class="atitle"><strong><font size="4">示例：MessagesProcessor</font></strong></span></a></p><p>为了向您展示 <code><font face="Courier" size="2">Consumer</font></code> 类是如何工作的，我们将使用一个简单示例。 <code><font face="Courier" size="2">MessagesProcessor</font></code> 类以异步方式处理进入的消息（也就是说，不干扰调用线程）。其工作是在每个消息到来时打印它。 <code><font face="Courier" size="2">MessagesProcessor</font></code> 具有一个处理到来的消息作业的内部 <code><font face="Courier" size="2">Consumer</font></code> 。当新作业进入空队列时， <code><font face="Courier" size="2">Consumer</font></code> 调用 <code><font face="Courier" size="2">processMessage(String)</font></code> 方法来处理它，如清单 4 所示： </p><br /><a name="code4"><b>清单 4. MessagesProcessor 类</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">      class MessagesProcessor
      {
         String _name;
         // anonymous inner class that supplies the consumer
         // capabilities for the MessagesProcessor
         private Consumer _consumer = new Consumer()
         {
            // that method is called on each event retrieved
            protected void onConsume(Object o)
            {
               if (!(o instanceof String))
               {
                  System.out.println("illegal use, ignoring");
                  return;
               }
               MessagesProcesser.this.processMessage((String)o);
            }
         }.setName("MessagesProcessor").init();

         public void gotMessageEvent(String s)
         {
            _consumer.add(s);
         }
         private void processMessage(String s)
         {
            System.out.println(_name+" processed message: "+s);
         }

         private void terminate()
         {
           _consumer.terminateWait();
           _name = null;
         }

         MessagesProcessor()
         {
            _name = "Example Consumer";
         }
      }
</font></code></pre></td></tr></tbody></table><br /><p>正如您可以从上面的代码中所看到的，定制 <code><font face="Courier" size="2">Consumer</font></code> 相当简单。我们使用了一个匿名内部类来继承 <code><font face="Courier" size="2">Consumer</font></code> 类，并重载抽象方法 <code><font face="Courier" size="2">onConsume()</font></code> 。因此，在我们的示例中，只需调用 <code><font face="Courier" size="2">processMessage</font></code> 。 </p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="7"><span class="atitle"><strong><font size="4">Consumer 类的高级特性</font></strong></span></a></p><p>除了开始时提出的基本需求以外，我们还为 <code><font face="Courier" size="2">Consumer</font></code> 类提供了一些我们觉得有用的高级特性。 </p><p><b>事件通知</b><br /></p><ul><li><b>onThreadTerminate()</b>：只在终止 <code><font face="Courier" size="2">Consumer</font></code> 前调用该方法。我们出于调试目的覆盖了这个方法。 <br /></li><li><b>goingToRest()</b>：只在 Consumer 线程进入休眠前调用该方法（也就是说，只在调用 <code><font face="Courier" size="2">_waitForJobsMonitor.wait()</font></code> 之前调用）。只在需要消费者在进入休眠之前处理一批已处理工作的复杂情况中，可能需要这种通知。 </li></ul><p></p><p><b>终止</b><br /></p><ul><li><b>terminate()</b>：Consumer 线程的异步终止。 <br /></li><li><b>terminateWait()</b>：设置调用线程一直等待，直到消费者线程实际终止为止。 </li></ul><p></p><p>在我们的示例中，如果使用 <code><font face="Courier" size="2">terminate()</font></code> 而不是 <code><font face="Courier" size="2">terminateWait()</font></code> ，那么将会出现问题，因为在将 <code><font face="Courier" size="2">_name</font></code> 设置成空值之后调用 <code><font face="Courier" size="2">onConsume()</font></code> 方法。这将导致执行 <code><font face="Courier" size="2">processMessage</font></code> 的线程抛出一个 <code><font face="Courier" size="2">NullPointerException</font></code> 。 </p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#main"><b><font face="Verdana" color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="8"><span class="atitle"><strong><font size="4">结束语：Consumer 类的好处</font></strong></span></a></p><p>可在 <a href="http://www-128.ibm.com/developerworks/cn/java/j-prodcon/#resources"><font color="#996699">参考资料</font></a>一节下载 <code><font face="Courier" size="2">Consumer</font></code> 类的源代码。请自由使用源代码，并按照您的需要扩展它。我们发现将这个类用于多线程应用程序开发有许多好处： 
</p><ul><li><b>代码重用／重复代码的消除</b>：如果您有 <code><font face="Courier" size="2">Consumer</font></code> 类，就不必为您应用程序中的每个实例编写一个新的消费者。如果在应用程序开发中频繁使用生产者-消费者方案，这可以很大程度地节省时间。另外，请牢记重复代码是滋生错误的沃土。它还使基本代码的维护更为困难。 <br /></li><li><b>更少错误</b>：使用验证过的代码是一种防止错误的好实践，尤其是处理多线程应用程序时。因为 <code><font face="Courier" size="2">Consumer</font></code> 类已经被调试过，所以它更安全。消费者还通过在线程和资源之间担任安全中介来防止与线程相关的错误。消费者可以代表其它线程以顺序的方式访问资源。 <br /></li><li><b>漂亮、清晰的代码</b>：使用 <code><font face="Courier" size="2">Consumer</font></code> 类有助于我们编写出更简单的代码，这样的代码更容易理解和维护。如果我们不使用 <code><font face="Courier" size="2">Consumer</font></code> 类，就必须编写代码来处理两种不同的功能：消费逻辑（队列和线程管理、同步等）和指定消费者的用法或功能的代码。 </li></ul><img src ="http://www.blogjava.net/lijiajia418/aggbug/65605.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:53 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65605.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 您的小数点到哪里去了？</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65603.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:51:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65603.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65603.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65603.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65603.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65603.html</trackback:ping><description><![CDATA[
		<blockquote>许多程序员在其整个开发生涯中都不曾使用定点或浮点数，可能的例外是，偶尔在计时测试或基准测试程序中会用到。Java语言和类库支持两类非整数类型 ― IEEE 754 浮点（ <code><font face="Courier" size="2">float</font></code> 和 <code><font face="Courier" size="2">double</font></code> ，包装类（wrapper class）为 <code><font face="Courier" size="2">Float</font></code> 和 <code><font face="Courier" size="2">Double</font></code> ），以及任意精度的小数（ <code><font face="Courier" size="2">java.math.BigDecimal</font></code> ）。在本月的 <i>Java 理论和实践</i>中，Brian Goetz 探讨了在 Java 程序中使用非整数类型时一些常碰到的陷阱和“gotcha”。请在本文的 <a href="javascript:void forumWindow()"><font color="#996699">论坛</font></a>上提出您对本文的想法，以飨笔者和其他读者。（您也可以单击本文顶部或底部的讨论来访问论坛）。 </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>虽然几乎每种处理器和编程语言都支持浮点运算，但大多数程序员很少注意它。这容易理解 ― 我们中大多数很少需要使用非整数类型。除了科学计算和偶尔的计时测试或基准测试程序，其它情况下几乎都用不着它。同样，大多数开发人员也容易忽略 <code><font face="Courier" size="2">java.math.BigDecimal</font></code> 所提供的任意精度的小数 ― 大多数应用程序不使用它们。然而，在以整数为主的程序中有时确实会出人意料地需要表示非整型数据。例如，JDBC 使用 <code><font face="Courier" size="2">BigDecimal</font></code> 作为 SQL <code><font face="Courier" size="2">DECIMAL</font></code> 列的首选互换格式。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">IEEE 浮点</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Java 语言支持两种基本的浮点类型： <code><font face="Courier" size="2">float</font></code> 和 <code><font face="Courier" size="2">double</font></code> ，以及与它们对应的包装类 <code><font face="Courier" size="2">Float</font></code> 和 <code><font face="Courier" size="2">Double</font></code> 。它们都依据 IEEE 754 标准，该标准为 32 位浮点和 64 位双精度浮点二进制小数定义了二进制标准。 </p>
		<p>IEEE 754 用科学记数法以底数为 2 的小数来表示浮点数。IEEE 浮点数用 1 位表示数字的符号，用 8 位来表示指数，用 23 位来表示尾数，即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制（底数 2）小数来表示，这意味着最高位对应着值 ?(2 <sup>-1</sup>)，第二位对应着 ?(2 <sup>-2</sup>)，依此类推。对于双精度浮点数，用 11 位表示指数，52 位表示尾数。IEEE 浮点值的格式如图 1 所示。 </p>
		<br />
		<a name="IDADA0PE">
				<b>图 1. IEEE 754 浮点数的格式 </b>
		</a>
		<br />
		<img height="177" alt="图 1. IEEE 754 浮点数的格式" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp0114/float.gif" width="579" />
		<br />
		<p>因为用科学记数法可以有多种方式来表示给定数字，所以要规范化浮点数，以便用底数为 2 并且小数点左边为 1 的小数来表示，按照需要调节指数就可以得到所需的数字。所以，例如，数 1.25 可以表示为尾数为 1.01，指数为 0： <code><font face="Courier" size="2">(-1) <sup>0</sup>*1.01 <sub>2</sub>*2 <sup>0</sup></font></code></p>
		<p>数 10.0 可以表示为尾数为 1.01，指数为 3： <code><font face="Courier" size="2">(-1) <sup>0</sup>*1.01 <sub>2</sub>*2 <sup>3</sup></font></code></p>
		<p>
				<a name="N100C8">
						<span class="smalltitle">
								<strong>特殊数字</strong>
						</span>
				</a>
		</p>
		<p>除了编码所允许的值的标准范围（对于 <code><font face="Courier" size="2">float</font></code> ，从 1.4e-45 到 3.4028235e+38），还有一些表示无穷大、负无穷大、 <code><font face="Courier" size="2">-0</font></code> 和 NaN（它代表“不是一个数字”）的特殊值。这些值的存在是为了在出现错误条件（譬如算术溢出，给负数开平方根，除以 <code><font face="Courier" size="2">0</font></code> 等）下，可以用浮点值集合中的数字来表示所产生的结果。 </p>
		<p>这些特殊的数字有一些不寻常的特征。例如， <code><font face="Courier" size="2">0</font></code> 和 <code><font face="Courier" size="2">-0</font></code> 是不同值，但在比较它们是否相等时，被认为是相等的。用一个非零数去除以无穷大的数，结果等于 <code><font face="Courier" size="2">0</font></code> 。特殊数字 NaN 是无序的；使用 <code><font face="Courier" size="2">==</font></code> 、 <code><font face="Courier" size="2">&lt;</font></code> 和 <code><font face="Courier" size="2">&gt;</font></code> 运算符将 NaN 与其它浮点值比较时，结果为 <code><font face="Courier" size="2">false</font></code> 。如果 <code><font face="Courier" size="2">f</font></code> 为 NaN，则即使 <code><font face="Courier" size="2">(f == f)</font></code> 也会得到 <code><font face="Courier" size="2">false</font></code> 。如果想将浮点值与 NaN 进行比较，则使用 <code><font face="Courier" size="2">Float.isNaN()</font></code> 方法。表 1 显示了无穷大和 NaN 的一些属性。 </p>
		<p>
				<a name="N1010C">
						<span class="smalltitle">
								<strong>表 1. 特殊浮点值的属性</strong>
						</span>
				</a>
		</p>
		<p>
		</p>
		<table cellspacing="6" cellpadding="0" border="0">
				<tbody>
						<tr>
								<td>
										<strong>表达式</strong>
								</td>
								<td>
										<b>结果</b>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">Math.sqrt(-1.0)</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; NaN</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">0.0 / 0.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; NaN</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">1.0 / 0.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; 无穷大</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">-1.0 / 0.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; 负无穷大</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">NaN + 1.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; NaN</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">无穷大 + 1.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; 无穷大</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">无穷大 + 无穷大</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; 无穷大</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">NaN &gt; 1.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; false</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">NaN == 1.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; false</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">NaN &lt; 1.0</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; false</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">NaN == NaN</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; false</font>
										</code>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">0.0 == -0.01</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-&gt; true</font>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<a name="N101F6">
						<span class="smalltitle">
								<strong>基本浮点类型和包装类浮点有不同的比较行为</strong>
						</span>
				</a>
		</p>
		<p>使事情更糟的是，在基本 <code><font face="Courier" size="2">float</font></code> 类型和包装类 <code><font face="Courier" size="2">Float</font></code> 之间，用于比较 NaN 和 <code><font face="Courier" size="2">-0</font></code> 的规则是不同的。对于 <code><font face="Courier" size="2">float</font></code> 值，比较两个 NaN 值是否相等将会得到 <code><font face="Courier" size="2">false</font></code> ，而使用 <code><font face="Courier" size="2">Float.equals()</font></code> 来比较两个 NaN <code><font face="Courier" size="2">Float</font></code> 对象会得到 <code><font face="Courier" size="2">true</font></code> 。造成这种现象的原因是，如果不这样的话，就不可能将 NaN <code><font face="Courier" size="2">Float</font></code> 对象用作 <code><font face="Courier" size="2">HashMap</font></code> 中的键。类似的，虽然 <code><font face="Courier" size="2">0</font></code> 和 <code><font face="Courier" size="2">-0</font></code> 在表示为浮点值时，被认为是相等的，但使用 <code><font face="Courier" size="2">Float.compareTo()</font></code> 来比较作为 <code><font face="Courier" size="2">Float</font></code> 对象的 <code><font face="Courier" size="2">0</font></code> 和 <code><font face="Courier" size="2">-0</font></code> 时，会显示 <code><font face="Courier" size="2">-0</font></code> 小于 <code><font face="Courier" size="2">0</font></code> 。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0114/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">浮点中的危险</font>
								</strong>
						</span>
				</a>
		</p>
		<p>由于无穷大、NaN 和 <code><font face="Courier" size="2">0</font></code> 的特殊行为，当应用浮点数时，可能看似无害的转换和优化实际上是不正确的。例如，虽然好象 <code><font face="Courier" size="2">0.0-f</font></code> 很明显等于 <code><font face="Courier" size="2">-f</font></code> ，但当 <code><font face="Courier" size="2">f</font></code> 为 <code><font face="Courier" size="2">0</font></code> 时，这是不正确的。还有其它类似的 gotcha，表 2 显示了其中一些 gotcha。 </p>
		<p>
				<a name="N10264">
						<span class="smalltitle">
								<strong>表 2. 无效的浮点假定</strong>
						</span>
				</a>
		</p>
		<p>
		</p>
		<table cellspacing="6" cellpadding="0" border="0">
				<tbody>
						<tr>
								<td>
										<strong>这个表达式……</strong>
								</td>
								<td>
										<b>不一定等于……</b>
								</td>
								<td>
										<b>当……</b>
								</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">0.0 - f</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">-f</font>
										</code>
								</td>
								<td>f 为 <code><font face="Courier" size="2">0</font></code></td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">f &lt; g</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">! (f &gt;= g)</font>
										</code>
								</td>
								<td>f 或 g 为 NaN</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">f == f</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">true</font>
										</code>
								</td>
								<td>f 为 NaN</td>
						</tr>
						<tr>
								<td>
										<code>
												<font face="Courier" size="2">f + g - g</font>
										</code>
								</td>
								<td>
										<code>
												<font face="Courier" size="2">f</font>
										</code>
								</td>
								<td>g 为无穷大或 NaN</td>
						</tr>
				</tbody>
		</table>
		<p>
				<a name="N102DC">
						<span class="smalltitle">
								<strong>舍入误差</strong>
						</span>
				</a>
		</p>
		<p>浮点运算很少是精确的。虽然一些数字（譬如 <code><font face="Courier" size="2">0.5</font></code> ）可以精确地表示为二进制（底数 2）小数（因为 <code><font face="Courier" size="2">0.5</font></code> 等于 2 <sup>-1</sup>），但其它一些数字（譬如 <code><font face="Courier" size="2">0.1</font></code> ）就不能精确的表示。因此，浮点运算可能导致舍入误差，产生的结果接近 ― 但不等于 ― 您可能希望的结果。例如，下面这个简单的计算将得到 <code><font face="Courier" size="2">2.600000000000001</font></code> ，而不是 <code><font face="Courier" size="2">2.6</font></code> ： </p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> double s=0;
  for (int i=0; i&lt;26; i++)
    s += 0.1;
  System.out.println(s);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>类似的， <code><font face="Courier" size="2">.1*26</font></code> 相乘所产生的结果不等于 <code><font face="Courier" size="2">.1</font></code> 自身加 26 次所得到的结果。当将浮点数强制转换成整数时，产生的舍入误差甚至更严重，因为强制转换成整数类型会舍弃非整数部分，甚至对于那些“看上去似乎”应该得到整数值的计算，也存在此类问题。例如，下面这些语句： </p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> double d = 29.0 * 0.01;
  System.out.println(d);
  System.out.println((int) (d * 100));
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>将得到以下输出：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> 0.29
  28
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这可能不是您起初所期望的。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0114/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">浮点数比较指南</font>
								</strong>
						</span>
				</a>
		</p>
		<p>由于存在 NaN 的不寻常比较行为和在几乎所有浮点计算中都不可避免地会出现舍入误差，解释浮点值的比较运算符的结果比较麻烦。</p>
		<p>最好完全避免使用浮点数比较。当然，这并不总是可能的，但您应该意识到要限制浮点数比较。如果必须比较浮点数来看它们是否相等，则应该将它们差的绝对值同一些预先选定的小正数进行比较，这样您所做的就是测试它们是否“足够接近”。（如果不知道基本的计算范围，可以使用测试“abs(a/b - 1) &lt; epsilon”，这种方法比简单地比较两者之差要更准确）。甚至测试看一个值是比零大还是比零小也存在危险 ―“以为”会生成比零略大值的计算事实上可能由于积累的舍入误差会生成略微比零小的数字。</p>
		<p>NaN 的无序性质使得在比较浮点数时更容易发生错误。当比较浮点数时，围绕无穷大和 NaN 问题，一种避免 gotcha 的经验法则是显式地测试值的有效性，而不是试图排除无效值。在清单 1 中，有两个可能的用于特性的 setter 的实现，该特性只能接受非负数值。第一个实现会接受 NaN，第二个不会。第二种形式比较好，因为它显式地检测了您认为有效的值的范围。</p>
		<br />
		<a name="IDA52XPE">
				<b>清单 1. 需要非负浮点值的较好办法和较差办法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">   // Trying to test by exclusion -- this doesn't catch NaN or infinity
    public void setFoo(float foo) {
      if (foo &lt; 0)
          throw new IllegalArgumentException(Float.toString(f));
        this.foo = foo;
    }
    // Testing by inclusion -- this does catch NaN
    public void setFoo(float foo) {
      if (foo &gt;= 0 &amp;&amp; foo &lt; Float.INFINITY)
        this.foo = foo;
  else
        throw new IllegalArgumentException(Float.toString(f));
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="N1033C">
						<span class="smalltitle">
								<strong>不要用浮点值表示精确值</strong>
						</span>
				</a>
		</p>
		<p>一些非整数值（如几美元和几美分这样的小数）需要很精确。浮点数不是精确值，所以使用它们会导致舍入误差。因此，使用浮点数来试图表示象货币量这样的精确数量不是一个好的想法。使用浮点数来进行美元和美分计算会得到灾难性的后果。浮点数最好用来表示象测量值这类数值，这类值从一开始就不怎么精确。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0114/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">用于较小数的 BigDecimal</font>
								</strong>
						</span>
				</a>
		</p>
		<p>从 JDK 1.3 起，Java 开发人员就有了另一种数值表示法来表示非整数： <code><font face="Courier" size="2">BigDecimal</font></code> 。 <code><font face="Courier" size="2">BigDecimal</font></code> 是标准的类，在编译器中不需要特殊支持，它可以表示任意精度的小数，并对它们进行计算。在内部，可以用任意精度任何范围的值和一个换算因子来表示 <code><font face="Courier" size="2">BigDecimal</font></code> ，换算因子表示左移小数点多少位，从而得到所期望范围内的值。因此，用 <code><font face="Courier" size="2">BigDecimal</font></code> 表示的数的形式为 <code><font face="Courier" size="2">unscaledValue*10 <sup>-scale</sup></font></code>。 </p>
		<p>用于加、减、乘和除的方法给 <code><font face="Courier" size="2">BigDecimal</font></code> 值提供了算术运算。由于 <code><font face="Courier" size="2">BigDecimal</font></code> 对象是不可变的，这些方法中的每一个都会产生新的 <code><font face="Courier" size="2">BigDecimal</font></code> 对象。因此，因为创建对象的开销， <code><font face="Courier" size="2">BigDecimal</font></code> 不适合于大量的数学计算，但设计它的目的是用来精确地表示小数。如果您正在寻找一种能精确表示如货币量这样的数值，则 <code><font face="Courier" size="2">BigDecimal</font></code> 可以很好地胜任该任务。 </p>
		<p>
				<a name="N1037C">
						<span class="smalltitle">
								<strong>所有的 equals 方法都不能真正测试相等</strong>
						</span>
				</a>
		</p>
		<p>如浮点类型一样， <code><font face="Courier" size="2">BigDecimal</font></code> 也有一些令人奇怪的行为。尤其在使用 <code><font face="Courier" size="2">equals()</font></code> 方法来检测数值之间是否相等时要小心。 <code><font face="Courier" size="2">equals()</font></code> 方法认为，两个表示同一个数但换算值不同（例如， <code><font face="Courier" size="2">100.00</font></code> 和 <code><font face="Courier" size="2">100.000</font></code> ）的 <code><font face="Courier" size="2">BigDecimal</font></code> 值是不相等的。然而， <code><font face="Courier" size="2">compareTo()</font></code> 方法会认为这两个数是相等的，所以在从数值上比较两个 <code><font face="Courier" size="2">BigDecimal</font></code> 值时，应该使用 <code><font face="Courier" size="2">compareTo()</font></code> 而不是 <code><font face="Courier" size="2">equals()</font></code> 。 </p>
		<p>另外还有一些情形，任意精度的小数运算仍不能表示精确结果。例如， <code><font face="Courier" size="2">1</font></code> 除以 <code><font face="Courier" size="2">9</font></code> 会产生无限循环的小数 <code><font face="Courier" size="2">.111111...</font></code> 。出于这个原因，在进行除法运算时， <code><font face="Courier" size="2">BigDecimal</font></code> 可以让您显式地控制舍入。 <code><font face="Courier" size="2">movePointLeft()</font></code> 方法支持 10 的幂次方的精确除法。 </p>
		<p>
				<a name="N103C4">
						<span class="smalltitle">
								<strong>使用 BigDecimal 作为互换类型</strong>
						</span>
				</a>
		</p>
		<p>SQL-92 包括 <code><font face="Courier" size="2">DECIMAL</font></code> 数据类型，它是用于表示定点小数的精确数字类型，它可以对小数进行基本的算术运算。一些 SQL 语言喜欢称此类型为 <code><font face="Courier" size="2">NUMERIC</font></code> 类型，其它一些 SQL 语言则引入了 <code><font face="Courier" size="2">MONEY</font></code> 数据类型，MONEY 数据类型被定义为小数点右侧带有两位的小数。 </p>
		<p>如果希望将数字存储到数据库中的 <code><font face="Courier" size="2">DECIMAL</font></code> 字段，或从 <code><font face="Courier" size="2">DECIMAL</font></code> 字段检索值，则如何确保精确地转换该数字？您可能不希望使用由 JDBC <code><font face="Courier" size="2">PreparedStatement</font></code> 和 <code><font face="Courier" size="2">ResultSet</font></code> 类所提供的 <code><font face="Courier" size="2">setFloat()</font></code> 和 <code><font face="Courier" size="2">getFloat()</font></code> 方法，因为浮点数与小数之间的转换可能会丧失精确性。相反，请使用 <code><font face="Courier" size="2">PreparedStatement</font></code> 和 <code><font face="Courier" size="2">ResultSet</font></code> 的 <code><font face="Courier" size="2">setBigDecimal()</font></code> 及 <code><font face="Courier" size="2">getBigDecimal()</font></code> 方法。 </p>
		<p>对于 <code><font face="Courier" size="2">BigDecimal</font></code> ，有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入，另一个以整数和换算因子作为输入，还有一个以小数的 <code><font face="Courier" size="2">String</font></code> 表示作为输入。要小心使用 <code><font face="Courier" size="2">BigDecimal(double)</font></code> 构造函数，因为如果不了解它，会在计算过程中产生舍入误差。请使用基于整数或 <code><font face="Courier" size="2">String </font></code>的构造函数。 </p>
		<p>
				<a name="N10417">
						<span class="smalltitle">
								<strong>构造 BigDecimal 数</strong>
						</span>
				</a>
		</p>
		<p>对于 <code><font face="Courier" size="2">BigDecimal</font></code> ，有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入，另一个以整数和换算因子作为输入，还有一个以小数的 <code><font face="Courier" size="2">String</font></code> 表示作为输入。要小心使用 <code><font face="Courier" size="2">BigDecimal(double)</font></code> 构造函数，因为如果不了解它，会在计算过程中产生舍入误差。请使用基于整数或 <code><font face="Courier" size="2">String</font></code> 的构造函数。 </p>
		<p>如果使用 <code><font face="Courier" size="2">BigDecimal(double)</font></code> 构造函数不恰当，在传递给 JDBC <code><font face="Courier" size="2">setBigDecimal()</font></code> 方法时，会造成似乎很奇怪的 JDBC 驱动程序中的异常。例如，考虑以下 JDBC 代码，该代码希望将数字 <code><font face="Courier" size="2">0.01</font></code> 存储到小数字段： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> PreparedStatement ps =
    connection.prepareStatement("INSERT INTO Foo SET name=?, value=?");
  ps.setString(1, "penny");
  ps.setBigDecimal(2, new BigDecimal(0.01));
  ps.executeUpdate();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在执行这段似乎无害的代码时会抛出一些令人迷惑不解的异常（这取决于具体的 JDBC 驱动程序），因为 <code><font face="Courier" size="2">0.01</font></code> 的双精度近似值会导致大的换算值，这可能会使 JDBC 驱动程序或数据库感到迷惑。JDBC 驱动程序会产生异常，但可能不会说明代码实际上错在哪里，除非意识到二进制浮点数的局限性。相反，使用 <code><font face="Courier" size="2">BigDecimal("0.01")</font></code> 或 <code><font face="Courier" size="2">BigDecimal(1, 2)</font></code> 构造 <code><font face="Courier" size="2">BigDecimal</font></code> 来避免这类问题，因为这两种方法都可以精确地表示小数。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0114/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在 Java 程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”，不能假定浮点计算一定产生整型或精确的结果，虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值，譬如测量。如果需要表示定点数（譬如，几美元和几美分），则使用 <code><font face="Courier" size="2">BigDecimal</font></code> 。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65603.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:51 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65603.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论和实践: 理解 JTS ― 幕后魔术</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65604.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:51:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65604.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65604.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65604.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65604.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65604.html</trackback:ping><description><![CDATA[
		<blockquote>虽然用 Java™ 语言编写的程序在理论上是不会出现“内存泄漏”的，但是有时对象在不再作为程序的逻辑状态的一部分之后仍然不被垃圾收集。本月，负责保障应用程序健康的工程师 Brian Goetz 探讨了无意识的对象保留的常见原因，并展示了如何用弱引用堵住泄漏。 </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>要让垃圾收集（GC）回收程序不再使用的对象，对象的<i>逻辑</i> 生命周期（应用程序使用它的时间）和对该对象拥有的引用的<i>实际</i> 生命周期必须是相同的。在大多数时候，好的软件工程技术保证这是自动实现的，不用我们对对象生命周期问题花费过多心思。但是偶尔我们会创建一个引用，它在内存中包含对象的时间比我们预期的要长得多，这种情况称为<i>无意识的对象保留（unintentional object retention）</i>。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">全局 Map 造成的内存泄漏</font>
								</strong>
						</span>
				</a>
		</p>
		<p>无意识对象保留最常见的原因是使用 <code><font face="Courier" size="2">Map</font></code> 将元数据与临时对象（transient object）相关联。假定一个对象具有中等生命周期，比分配它的那个方法调用的生命周期长，但是比应用程序的生命周期短，如客户机的套接字连接。需要将一些元数据与这个套接字关联，如生成连接的用户的标识。在创建 <code><font face="Courier" size="2">Socket</font></code> 时是不知道这些信息的，并且不能将数据添加到 <code><font face="Courier" size="2">Socket</font></code> 对象上，因为不能控制 <code><font face="Courier" size="2">Socket</font></code> 类或者它的子类。这时，典型的方法就是在一个全局 <code><font face="Courier" size="2">Map</font></code> 中存储这些信息，如清单 1 中的 <code><font face="Courier" size="2">SocketManager</font></code> 类所示： </p>
		<br />
		<a name="listing1">
				<b>清单 1. 使用一个全局 Map 将元数据关联到一个对象</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class SocketManager {
    private Map&lt;Socket,User&gt; m = new HashMap&lt;Socket,User&gt;();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}

SocketManager socketManager;
...
socketManager.setUser(socket, user);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩，但是除非准确地知道什么时候程序不再需要这个套接字，并记住从 <code><font face="Courier" size="2">Map</font></code> 中删除相应的映射，否则，<code><font face="Courier" size="2">Socket</font></code> 和 <code><font face="Courier" size="2">User</font></code> 对象将会永远留在 <code><font face="Courier" size="2">Map</font></code> 中，远远超过响应了请求和关闭套接字的时间。这会阻止 <code><font face="Courier" size="2">Socket</font></code> 和 <code><font face="Courier" size="2">User</font></code> 对象被垃圾收集，即使应用程序不会再使用它们。这些对象留下来不受控制，很容易造成程序在长时间运行后内存爆满。除了最简单的情况，在几乎所有情况下找出什么时候 <code><font face="Courier" size="2">Socket</font></code> 不再被程序使用是一件很烦人和容易出错的任务，需要人工对内存进行管理。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">找出内存泄漏</font>
								</strong>
						</span>
				</a>
		</p>
		<p>程序有内存泄漏的第一个迹象通常是它抛出一个 <code><font face="Courier" size="2">OutOfMemoryError</font></code>，或者因为频繁的垃圾收集而表现出糟糕的性能。幸运的是，垃圾收集可以提供能够用来诊断内存泄漏的大量信息。如果以 <code><font face="Courier" size="2">-verbose:gc</font></code> 或者 <code><font face="Courier" size="2">-Xloggc</font></code> 选项调用 JVM，那么每次 GC 运行时在控制台上或者日志文件中会打印出一个诊断信息，包括它所花费的时间、当前堆使用情况以及恢复了多少内存。记录 GC 使用情况并不具有干扰性，因此如果需要分析内存问题或者调优垃圾收集器，在生产环境中默认启用 GC 日志是值得的。 </p>
		<p>有工具可以利用 GC 日志输出并以图形方式将它显示出来，JTune 就是这样的一种工具（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#resources"><font color="#996699">参考资料</font></a>）。观察 GC 之后堆大小的图，可以看到程序内存使用的趋势。对于大多数程序来说，可以将内存使用分为两部分：<i>baseline</i> 使用和 <i>current load</i> 使用。对于服务器应用程序，baseline 使用就是应用程序在没有任何负荷、但是已经准备好接受请求时的内存使用，current load 使用是在处理请求过程中使用的、但是在请求处理完成后会释放的内存。只要负荷大体上是恒定的，应用程序通常会很快达到一个稳定的内存使用水平。如果在应用程序已经完成了其初始化并且负荷没有增加的情况下，内存使用持续增加，那么程序就可能在处理前面的请求时保留了生成的对象。 </p>
		<p>清单 2 展示了一个有内存泄漏的程序。<code><font face="Courier" size="2">MapLeaker</font></code> 在线程池中处理任务，并在一个 <code><font face="Courier" size="2">Map</font></code> 中记录每一项任务的状态。不幸的是，在任务完成后它不会删除那一项，因此状态项和任务对象（以及它们的内部状态）会不断地积累。 </p>
		<br />
		<a name="listing2">
				<b>清单 2. 具有基于 Map 的内存泄漏的程序</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map&lt;Task, TaskStatus&gt; taskStatus 
        = Collections.synchronizedMap(new HashMap&lt;Task, TaskStatus&gt;());
    private Random random = new Random();

    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };

    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];

        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }

    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>图 1 显示 <code><font face="Courier" size="2">MapLeaker</font></code> GC 之后应用程序堆大小随着时间的变化图。上升趋势是存在内存泄漏的警示信号。（在真实的应用程序中，坡度不会这么大，但是在收集了足够长时间的 GC 数据后，上升趋势通常会表现得很明显。） </p>
		<br />
		<a name="figure1">
				<b>图 1. 持续上升的内存使用趋势</b>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/heapsize.gif" />
		<br />
		<p>确信有了内存泄漏后，下一步就是找出哪种对象造成了这个问题。所有内存分析器都可以生成按照对象类进行分解的堆快照。有一些很好的商业堆分析工具，但是找出内存泄漏不一定要花钱买这些工具 —— 内置的 <code><font face="Courier" size="2">hprof</font></code> 工具也可完成这项工作。要使用 <code><font face="Courier" size="2">hprof</font></code> 并让它跟踪内存使用，需要以 <code><font face="Courier" size="2">-Xrunhprof:heap=sites</font></code> 选项调用 JVM。 </p>
		<p>清单 3 显示分解了应用程序内存使用的 <code><font face="Courier" size="2">hprof</font></code> 输出的相关部分。（<code><font face="Courier" size="2">hprof</font></code> 工具在应用程序退出时，或者用 <code><font face="Courier" size="2">kill -3</font></code> 或在 Windows 中按 Ctrl+Break 时生成使用分解。）注意两次快照相比，<code><font face="Courier" size="2">Map.Entry</font></code>、<code><font face="Courier" size="2">Task</font></code> 和 <code><font face="Courier" size="2">int[]</font></code> 对象有了显著增加。 </p>
		<p>请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/sidefile1.html"><font color="#5c81a7">清单 3</font></a>。</p>
		<p>清单 4 展示了 <code><font face="Courier" size="2">hprof</font></code> 输出的另一部分，给出了 <code><font face="Courier" size="2">Map.Entry</font></code> 对象的分配点的调用堆栈信息。这个输出告诉我们哪些调用链生成了 <code><font face="Courier" size="2">Map.Entry</font></code> 对象，并带有一些程序分析，找出内存泄漏来源一般来说是相当容易的。 </p>
		<br />
		<a name="listing4">
				<b>清单 4. HPROF 输出，显示 Map.Entry 对象的分配点</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">TRACE 300446:
	java.util.HashMap$Entry.&lt;init&gt;(&lt;Unknown Source&gt;:Unknown line)
	java.util.HashMap.addEntry(&lt;Unknown Source&gt;:Unknown line)
	java.util.HashMap.put(&lt;Unknown Source&gt;:Unknown line)
	java.util.Collections$SynchronizedMap.put(&lt;Unknown Source&gt;:Unknown line)
	com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
	com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">弱引用来救援了</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<code>
						<font face="Courier" size="2">SocketManager</font>
				</code> 的问题是 <code><font face="Courier" size="2">Socket</font></code>-<code><font face="Courier" size="2">User</font></code> 映射的生命周期应当与 <code><font face="Courier" size="2">Socket</font></code> 的生命周期相匹配，但是语言没有提供任何容易的方法实施这项规则。这使得程序不得不使用人工内存管理的老技术。幸运的是，从 JDK 1.2 开始，垃圾收集器提供了一种声明这种对象生命周期依赖性的方法，这样垃圾收集器就可以帮助我们防止这种内存泄漏 —— 利用<i>弱引用</i>。 </p>
		<p>弱引用是对一个对象（称为 <i>referent</i>）的引用的持有者。使用弱引用后，可以维持对 referent 的引用，而不会阻止它被垃圾收集。当垃圾收集器跟踪堆的时候，如果对一个对象的引用只有弱引用，那么这个 referent 就会成为垃圾收集的候选对象，就像没有任何剩余的引用一样，而且所有剩余的弱引用都被<i>清除</i>。（只有弱引用的对象称为<i>弱可及（weakly reachable）</i>。） </p>
		<p>
				<code>
						<font face="Courier" size="2">WeakReference</font>
				</code> 的 referent 是在构造时设置的，在没有被清除之前，可以用 <code><font face="Courier" size="2">get()</font></code> 获取它的值。如果弱引用被清除了（不管是 referent 已经被垃圾收集了，还是有人调用了 <code><font face="Courier" size="2">WeakReference.clear()</font></code>），<code><font face="Courier" size="2">get()</font></code> 会返回 <code><font face="Courier" size="2">null</font></code>。相应地，在使用其结果之前，应当总是检查 <code><font face="Courier" size="2">get()</font></code> 是否返回一个非 null 值，因为 referent 最终总是会被垃圾收集的。 </p>
		<p>用一个普通的（强）引用拷贝一个对象引用时，限制 referent 的生命周期至少与被拷贝的引用的生命周期一样长。如果不小心，那么它可能就与程序的生命周期一样 —— 如果将一个对象放入一个全局集合中的话。另一方面，在创建对一个对象的弱引用时，完全没有扩展 referent 的生命周期，只是在<i>对象仍然存活的时候</i>，保持另一种到达它的方法。 </p>
		<p>弱引用对于构造弱集合最有用，如那些在应用程序的其余部分使用对象期间存储关于这些对象的元数据的集合 —— 这就是 <code><font face="Courier" size="2">SocketManager</font></code> 类所要做的工作。因为这是弱引用最常见的用法，<code><font face="Courier" size="2">WeakHashMap</font></code> 也被添加到 JDK 1.2 的类库中，它对键（而不是对值）使用弱引用。如果在一个普通 <code><font face="Courier" size="2">HashMap</font></code> 中用一个对象作为键，那么这个对象在映射从 <code><font face="Courier" size="2">Map</font></code> 中删除之前不能被回收，<code><font face="Courier" size="2">WeakHashMap</font></code> 使您可以用一个对象作为 <code><font face="Courier" size="2">Map</font></code> 键，同时不会阻止这个对象被垃圾收集。清单 5 给出了 <code><font face="Courier" size="2">WeakHashMap</font></code> 的 <code><font face="Courier" size="2">get()</font></code> 方法的一种可能实现，它展示了弱引用的使用： </p>
		<br />
		<a name="listing5">
				<b>清单 5. WeakReference.get() 的一种可能实现</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class WeakHashMap&lt;K,V&gt; implements Map&lt;K,V&gt; {

    private static class Entry&lt;K,V&gt; extends WeakReference&lt;K&gt; 
      implements Map.Entry&lt;K,V&gt; {
        private V value;
        private final int hash;
        private Entry&lt;K,V&gt; next;
        ...
    }

    public V get(Object key) {
        int hash = getHash(key);
        Entry&lt;K,V&gt; e = getChain(hash);
        while (e != null) {
            K eKey= e.get();
            if (e.hash == hash &amp;&amp; (key == eKey || key.equals(eKey)))
                return e.value;
            e = e.next;
        }
        return null;
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>调用 <code><font face="Courier" size="2">WeakReference.get()</font></code> 时，它返回一个对 referent 的强引用（如果它仍然存活的话），因此不需要担心映射在 <code><font face="Courier" size="2">while</font></code> 循环体中消失，因为强引用会防止它被垃圾收集。<code><font face="Courier" size="2">WeakHashMap</font></code> 的实现展示了弱引用的一种常见用法 —— 一些内部对象扩展 <code><font face="Courier" size="2">WeakReference</font></code>。其原因在下面一节讨论引用队列时会得到解释。</p>
		<p>在向 <code><font face="Courier" size="2">WeakHashMap</font></code> 中添加映射时，请记住映射可能会在以后“脱离”，因为键被垃圾收集了。在这种情况下，<code><font face="Courier" size="2">get()</font></code> 返回 <code><font face="Courier" size="2">null</font></code>，这使得测试 <code><font face="Courier" size="2">get()</font></code> 的返回值是否为 <code><font face="Courier" size="2">null</font></code> 变得比平时更重要了。 </p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>用 WeakHashMap 堵住泄漏</strong>
						</span>
				</a>
		</p>
		<p>在 <code><font face="Courier" size="2">SocketManager</font></code> 中防止泄漏很容易，只要用 <code><font face="Courier" size="2">WeakHashMap</font></code> 代替 <code><font face="Courier" size="2">HashMap</font></code> 就行了，如清单 6 所示。（如果 <code><font face="Courier" size="2">SocketManager</font></code> 需要线程安全，那么可以用 <code><font face="Courier" size="2">Collections.synchronizedMap()</font></code> 包装 <code><font face="Courier" size="2">WeakHashMap</font></code>）。当映射的生命周期必须与键的生命周期联系在一起时，可以使用这种方法。不过，应当小心不滥用这种技术，大多数时候还是应当使用普通的 <code><font face="Courier" size="2">HashMap</font></code> 作为 <code><font face="Courier" size="2">Map</font></code> 的实现。 </p>
		<br />
		<a name="listing6">
				<b>清单 6. 用 WeakHashMap 修复 SocketManager</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class SocketManager {
    private Map&lt;Socket,User&gt; m = new WeakHashMap&lt;Socket,User&gt;();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="3.2">
						<span class="smalltitle">
								<strong>引用队列</strong>
						</span>
				</a>
		</p>
		<p>
				<code>
						<font face="Courier" size="2">WeakHashMap</font>
				</code> 用弱引用承载映射键，这使得应用程序不再使用键对象时它们可以被垃圾收集，<code><font face="Courier" size="2">get()</font></code> 实现可以根据 <code><font face="Courier" size="2">WeakReference.get()</font></code> 是否返回 <code><font face="Courier" size="2">null</font></code> 来区分死的映射和活的映射。但是这只是防止 <code><font face="Courier" size="2">Map</font></code> 的内存消耗在应用程序的生命周期中不断增加所需要做的工作的一半，还需要做一些工作以便在键对象被收集后从 <code><font face="Courier" size="2">Map</font></code> 中删除死项。否则，<code><font face="Courier" size="2">Map</font></code> 会充满对应于死键的项。虽然这对于应用程序是不可见的，但是它仍然会造成应用程序耗尽内存，因为即使键被收集了，<code><font face="Courier" size="2">Map.Entry</font></code> 和值对象也不会被收集。 </p>
		<p>可以通过周期性地扫描 <code><font face="Courier" size="2">Map</font></code>，对每一个弱引用调用 <code><font face="Courier" size="2">get()</font></code>，并在 get() 返回 <code><font face="Courier" size="2">null</font></code> 时删除那个映射而消除死映射。但是如果 <code><font face="Courier" size="2">Map</font></code> 有许多活的项，那么这种方法的效率很低。如果有一种方法可以在弱引用的 referent 被垃圾收集时发出通知就好了，这就是<i>引用队列</i> 的作用。 </p>
		<p>引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的主要方法。弱引用有两个构造函数：一个只取 referent 作为参数，另一个还取引用队列作为参数。如果用关联的引用队列创建弱引用，在 referent 成为 GC 候选对象时，这个引用对象（不是 referent）就在引用清除后<i>加入</i> 到引用队列中。之后，应用程序从引用队列提取引用并了解到它的 referent 已被收集，因此可以进行相应的清理活动，如去掉已不在弱集合中的对象的项。（引用队列提供了与 <code><font face="Courier" size="2">BlockingQueue</font></code> 同样的出列模式 —— polled、timed blocking 和 untimed blocking。） </p>
		<p>
				<code>
						<font face="Courier" size="2">WeakHashMap</font>
				</code> 有一个名为 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 的私有方法，大多数 <code><font face="Courier" size="2">Map</font></code> 操作中会调用它，它去掉引用队列中所有失效的引用，并删除关联的映射。清单 7 展示了 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 的一种可能实现。用于存储键-值映射的 <code><font face="Courier" size="2">Entry</font></code> 类型扩展了 <code><font face="Courier" size="2">WeakReference</font></code>，因此当 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 要求下一个失效的弱引用时，它得到一个 <code><font face="Courier" size="2">Entry</font></code>。用引用队列代替定期扫描内容的方法来清理 <code><font face="Courier" size="2">Map</font></code> 更有效，因为清理过程不会触及活的项，只有在有实际加入队列的引用时它才工作。 </p>
		<br />
		<a name="listing7">
				<b>清单 7. WeakHashMap.expungeStaleEntries() 的可能实现</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    private void expungeStaleEntries() {
	Entry&lt;K,V&gt; e;
        while ( (e = (Entry&lt;K,V&gt;) queue.poll()) != null) {
            int hash = e.hash;

            Entry&lt;K,V&gt; prev = getChain(hash);
            Entry&lt;K,V&gt; cur = prev;
            while (cur != null) {
                Entry&lt;K,V&gt; next = cur.next;
                if (cur == e) {
                    if (prev == e)
                        setChain(hash, next);
                    else
                        prev.next = next;
                    break;
                }
                prev = cur;
                cur = next;
            }
        }
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>弱引用和弱集合是对堆进行管理的强大工具，使得应用程序可以使用更复杂的可及性方案，而不只是由普通（强）引用所提供的“要么全部要么没有”可及性。下个月，我们将分析与弱引用有关的<i>软引用</i>，将分析在使用弱引用和软引用时，垃圾收集器的行为。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65604.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:51 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65604.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JDBC 查询日志变得简单</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65601.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:50:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65601.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65601.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65601.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65601.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65601.html</trackback:ping><description><![CDATA[
		<blockquote>JDBC java.sql.PreparedStatement接口的简单扩展可以使查询记录更少犯错，同时整理您的代码。在本文中，IBM电子商务顾问Jens Wyke向您介绍如何应用基本的封装技术(“通过封装来实现扩展”也称为Decorator设计模式)来获得最满意的结果。</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>在大多数情况下，JDBC <code><font face="Courier" size="2">PreparedStatements </font></code>使执行数据库查询更简便并可以显著提升您整体应用程序的性能。当谈到日志查询语句时 <code><font face="Courier" size="2">PreparedStatement</font></code> 接口就显得有些不足了。 <code><font face="Courier" size="2">PreparedStatement</font></code> 的优势在于其可变性，但是一个好的日志条目必须正确描述如何将SQL发送到数据库，它将密切关注用实际的参数值来替换所有参数占位符。虽然有多种方法可以解决这一难题，但没有任何一种易于大规模实施并且大部分将扰乱您的程序代码。 </p>
		<p>在本文中，您将了解到如何扩展JDBC <code><font face="Courier" size="2">PreparedStatement</font></code> 接口来进行查询日志。 <code><font face="Courier" size="2">LoggableStatement</font></code> 类实现 <code><font face="Courier" size="2">PreparedStatement</font></code> 接口，但添加用于获得查询字符串的方法，使用一种适用于记录的格式。使用 <code><font face="Courier" size="2">LoggableStatement</font></code> 类可以减少日志代码中发生错误的几率，生成简单且易于管理的代码。 </p>
		<p>注意：本文假设您有丰富的JDBC和 <code><font face="Courier" size="2">PreparedStatement</font></code> 类经验。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">典型日志解决方案</font>
								</strong>
						</span>
				</a>
		</p>
		<p>表1介绍了数据库查询时通常是如何使用 <code><font face="Courier" size="2">PreparedStatement</font></code> (虽然忽略了初始化和错误处理)。在本文中，我们将使用SQL query <code><font face="Courier" size="2">SELECT</font></code> 做为例子，但讨论使用其它类型的SQL语句，如 <code><font face="Courier" size="2">DELETE</font></code> 、 <code><font face="Courier" size="2">UPDATE</font></code> 和 <code><font face="Courier" size="2">INSERT</font></code> 。 </p>
		<br />
		<a name="IDA1DCIC">
				<b>表1：一个典型的SQL数据库查询</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">String sql = "select foo, bar from foobar where foo &lt; ? and bar = ?";
    String fooValue = new Long(99);
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    ResultSet rs = pstmt.executeQuery();

    // parse result...

</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>表1中一个好的查询日志条目看起来应与下面有几分类似：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">Executing query: select foo,bar from foobar where foo &lt; 99 and 
bar='christmas'
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>下面是查询的日志代码的一个例子。注意：表1中的问号已经被每个参数的值替换。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">System.out.println("Executing query: select foo, bar from foobar where foo
&lt; "+fooValue+" and bar = '+barValue+"'")
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>一种更好的方法是创建方法，我们称之为 <code><font face="Courier" size="2">replaceFirstQuestionMark</font></code> ，它读取查询字符串并用参数值替换问号，如表2所示。这类方法的使用无需创建复制的字符串来描述SQL语句。 </p>
		<br />
		<a name="IDATECIC">
				<b>表 2：使用replaceFirstQuestionMark来进行字符串替换</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">      // listing 1 goes here

     sql = replaceFirstQuestionMark(sql, fooValue);
     sql = replaceFirstQuestionMark(sql, barValue);
     System.out.println("Executing query: "+sql);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>虽然这些解决方案都易于实施，但没有一种是完美的。问题是在更改SQL模板的同时也必须更改日志代码。您将在某一点上犯错几乎是不可避免的。查询将更改但您忘记了更新日志代码，您将结束与将发送到数据库的查询不匹配的日志条目 -- 调试恶梦。</p>
		<p>我们真正需要的是一种使我们能够一次性使用每个参数变量(在我们的实例中为 <code><font face="Courier" size="2">fooValue</font></code> 和 <code><font face="Courier" size="2">barValue</font></code> )的设计方案。我们希望有一种方法，它使我们能够获得查询字符串，并用实际的参数值替换参数占位符。由于 <code><font face="Courier" size="2">java.sql.PreparedStatement</font></code> 没有此类方法，我们必须自己实现。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-loggable/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">定制解决方案</font>
								</strong>
						</span>
				</a>
		</p>
		<p>我们的 <code><font face="Courier" size="2">PreparedStatement</font></code> 定制实施将做为围绕JDBC驱动器提供的“真实语句（real statement）”的封装器（Wrapper)。封装器语句将转发所有方法调用(例如 <code><font face="Courier" size="2">setLong(int, long)</font></code> 和 <code><font face="Courier" size="2">setString(int,String)</font></code> ) 到“真实语句”。在这样做之前它将保存相关的参数值，从而它们可以用于生成日志输出结果。 </p>
		<p>表3介绍了 <code><font face="Courier" size="2">LoggableStatement</font></code> 类如何实现 <code><font face="Courier" size="2">java.sql.PreparedStatement</font></code> ，以及它如何使用JDBC连接和SQL模板作为输入来构建。 </p>
		<br />
		<a name="IDALGCIC">
				<b>表3：LoggableStatement实现java.sql.PreparedStatement</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  public class LoggableStatement implements java.sql.PreparedStatement {

     // used for storing parameter values needed
      // for producing log
     private ArrayList parameterValues;     
          
     // the query string with question marks as  
     // parameter placeholders
     private String sqlTemplate;       
               
     // a statement created from a real database     
     // connection                                       
     private PreparedStatement wrappedStatement; 
                                                 

    public LoggableStatement(Connection connection, String sql) 
      throws SQLException {
      // use connection to make a prepared statement
      wrappedStatement = connection.prepareStatement(sql);
      sqlTemplate = sql;
      parameterValues = new ArrayList();
    }
     }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-loggable/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">LoggableStatement如何工作</font>
								</strong>
						</span>
				</a>
		</p>
		<p>表4介绍了 <code><font face="Courier" size="2">LoggableStatement</font></code> 如何向 <code><font face="Courier" size="2">saveQueryParamValue()</font></code> 方法添加一个调用，以及在方法 <code><font face="Courier" size="2">setLong</font></code> 和 <code><font face="Courier" size="2">setString</font></code> 的“真实语句”上调用相应的方法。我们采用与用于参数设置的所有方法(例如 <code><font face="Courier" size="2">setChar</font></code> 、 <code><font face="Courier" size="2">setLong</font></code> 、 <code><font face="Courier" size="2">setRef</font></code> 和 <code><font face="Courier" size="2">setObj</font></code> )相同的方式来增加 <code><font face="Courier" size="2">saveQueryParamValue()</font></code> 调用。表4还显示了在不调用 <code><font face="Courier" size="2">saveQueryParamValue()</font></code> 的情况下如何封装方法 <code><font face="Courier" size="2">executeQuery</font></code> ，因为它不是一个“参数设置”方法。 </p>
		<br />
		<a name="IDANQCIC">
				<b>表4：LoggableStatement 方法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">     public void setLong(int parameterIndex, long x) 
         throws java.sql.SQLException {
      wrappedStatement.setLong(parameterIndex, x);
      saveQueryParamValue(parameterIndex, new Long(x));
   }

   public void setString(int parameterIndex, String x) 
       throws java.sql.SQLException {
      wrappedStatement.setString(parameterIndex, x);
      saveQueryParamValue(parameterIndex, x);
   }

  public ResultSet executeQuery() throws java.sql.SQLException {
     return wrappedStatement.executeQuery();
   }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>表5中显示了 <code><font face="Courier" size="2">saveQueryParamValue()</font></code> 方法。它把每个参数值转换成 <code><font face="Courier" size="2">String</font></code> 表示，保存以便 <code><font face="Courier" size="2">getQueryString</font></code> 方法日后使用。缺省情况下，一个对象使用其 <code><font face="Courier" size="2">toString</font></code> 方法将被转换成 <code><font face="Courier" size="2">String</font></code> ，但如果对象是 <code><font face="Courier" size="2">String</font></code> 或 <code><font face="Courier" size="2">Date</font></code> ，它将用单引号('')表示。 <code><font face="Courier" size="2">getQueryString()</font></code> 方法使您能够从日志复制大多数查询并进行粘贴，无需修改交互式SQL处理器就可进行测试和调试。您可以根据需要修订该方法来转换其它类的参数值。 </p>
		<br />
		<a name="IDAZRCIC">
				<b>表5：saveQueryParamValue()方法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  private void saveQueryParamValue(int position, Object obj) {
      String strValue;
      if (obj instanceof String || obj instanceof Date) {
           // if we have a String, include '' in the saved value
           strValue = "'" + obj + "'";
      } else {
           if (obj == null) {
                // convert null to the string null
                 strValue = "null";
           } else {
                // unknown object (includes all Numbers), just call toString
                strValue = obj.toString();
           }
      }
      // if we are setting a position larger than current size of 
      // parameterValues, first make it larger
      while (position &gt;= parameterValues.size()) {
           parameterValues.add(null);
      }
      // save the parameter
      parameterValues.set(position, strValue);
 }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>当我们使用标准方法来设置所有参数时，我们在 <code><font face="Courier" size="2">LoggableStatement</font></code> 中简单调用 <code><font face="Courier" size="2">getQueryString()</font></code> 方法来获得查询字符串。所有问号都将被真正的参数值替换，它准备输出到我们选定的日志目的地。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-loggable/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">使用LoggableStatement</font>
								</strong>
						</span>
				</a>
		</p>
		<p>表6显示如何更改表1和表2中的代码来使用 <code><font face="Courier" size="2">LoggableStatement</font></code> 。将 <code><font face="Courier" size="2">LoggableStatement</font></code> 引入到我们的应用程序代码中可以解决复制的参数变量问题。如果改变了SQL模板，我们只需更新 <code><font face="Courier" size="2">PreparedStatement</font></code> 上的参数设置调用(例如添加一个 <code><font face="Courier" size="2">pstmt.setString(3,"new-param-value")</font></code> )。这一更改将在日志输出结果中反映出，无需任何记录代码的手工更新。 </p>
		<br />
		<a name="IDAGTCIC">
				<b>表6：使用LoggableStatement&amp;#160</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    String sql = "select foo, bar from foobar where foo &lt; ? and bar = ?";
    long fooValue = 99;
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt;

    if(logEnabled) // use a switch to toggle logging.
        pstmt = new LoggableStatement(conn,sql);
    else
        pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    if(logEnabled)
       System.out.println("Executing query: "+
         ((LoggableStatement)pstmt).getQueryString());

    ResultSet rs = pstmt.executeQuery();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-loggable/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>使用本文介绍的非常简单的步骤，您可以为查询记录扩展JDBC <code><font face="Courier" size="2">PreparedStatement</font></code> 接口。我们在此处使用的技术可以被视为“通过封装来实现扩展”，或作为Decorator设计模式的一个实例(见 <a href="http://www-128.ibm.com/developerworks/cn/java/j-loggable/#resources"><font color="#996699">参考资料</font></a>)。通过封装来实现扩展在当您必须扩展API但subclassing不是一项可选功能时极其有用。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65601.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:50 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65601.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 变还是不变？</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65602.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:50:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65602.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65602.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65602.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65602.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65602.html</trackback:ping><description><![CDATA[
		<blockquote>不变对象具有许多能更方便地使用它们的特性，包括不严格的同步需求和不必考虑数据讹误就能自由地共享和高速缓存对象引用。尽管不变性可能未必对于所有类都有意义，但大多数程序中至少有一些类将受益于不可变。在本月的 <i>Java 理论与实践</i>中，Brian Goetz 说明了不变性的一些长处和构造不变类的一些准则。请在附带的 <a href="javascript:void forumWindow()"><font color="#996699">论坛</font></a>中与作者和其他读者分享您关于本文的心得。（也可以单击文章顶部或底部的“讨论”来访问论坛。） </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>不变对象是指在实例化后其外部可见状态无法更改的对象。Java 类库中的 <code><font face="Courier" size="2">String</font></code> 、 <code><font face="Courier" size="2">Integer</font></code> 和 <code><font face="Courier" size="2">BigDecimal</font></code> 类就是不变对象的示例 ― 它们表示在对象的生命期内无法更改的单个值。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">不变性的长处</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果正确使用不变类，它们会极大地简化编程。因为它们只能处于一种状态，所以只要正确构造了它们，就决不会陷入不一致的状态。您不必复制或克隆不变对象，就能自由地共享和高速缓存对它们的引用；您可以高速缓存它们的字段或其方法的结果，而不用担心值会不会变成失效的或与对象的其它状态不一致。不变类通常产生最好的映射键。而且，它们本来就是线程安全的，所以不必在线程间同步对它们的访问。</p>
		<p>
				<a name="N1006C">
						<span class="smalltitle">
								<strong>自由高速缓存</strong>
						</span>
				</a>
		</p>
		<p>因为不变对象的值没有更改的危险，所以可以自由地高速缓存对它们的引用，而且可以肯定以后的引用仍将引用同一个值。同样地，因为它们的特性无法更改，所以您可以高速缓存它们的字段和其方法的结果。</p>
		<p>如果对象是可变的，就必须在存储对其的引用时引起注意。请考虑清单 1 中的代码，其中排列了两个由调度程序执行的任务。目的是：现在启动第一个任务，而在某一天启动第二个任务。</p>
		<br />
		<a name="IDAVCW0B">
				<b>清单 1. 可变的 Date 对象的潜在问题</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> Date d = new Date();
  Scheduler.scheduleTask(task1, d);
  d.setTime(d.getTime() + ONE_DAY);
  scheduler.scheduleTask(task2, d);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>因为 <code><font face="Courier" size="2">Date</font></code> 是可变的，所以 <code><font face="Courier" size="2">scheduleTask</font></code> 方法必须小心地用防范措施将日期参数复制（可能通过 <code><font face="Courier" size="2">clone()</font></code> ）到它的内部数据结构中。不然， <code><font face="Courier" size="2">task1</font></code> 和 <code><font face="Courier" size="2">task2</font></code> 可能都在明天执行，这可不是所期望的。更糟的是，任务调度程序所用的内部数据结构会变成讹误。在编写象 <code><font face="Courier" size="2">scheduleTask()</font></code> 这样的方法时，极其容易忘记用防范措施复制日期参数。如果忘记这样做，您就制造了一个难以捕捉的错误，这个错误不会马上显现出来，而且当它暴露时人们要花较长的时间才会捕捉到。不变的 <code><font face="Courier" size="2">Date</font></code> 类不可能发生这类错误。 </p>
		<p>
				<a name="N100A1">
						<span class="smalltitle">
								<strong>固有的线程安全</strong>
						</span>
				</a>
		</p>
		<p>大多数的线程安全问题发生在当多个线程正在试图并发地修改一个对象的状态（写-写冲突）时，或当一个线程正试图访问一个对象的状态，而另一个线程正在修改它（读-写冲突）时。要防止这样的冲突，必须同步对共享对象的访问，以便在对象处于不一致状态时其它线程不能访问它们。正确地做到这一点会很难，需要大量文档来确保正确地扩展程序，还可能对性能产生不利后果。只要正确构造了不变对象（这意味着不让对象引用从构造函数中转义），就使它们免除了同步访问的要求，因为无法更改它们的状态，从而就不可能存在写-写冲突或读-写冲突。</p>
		<p>不用同步就能自由地在线程间共享对不变对象的引用，可以极大地简化编写并发程序的过程，并减少程序可能存在的潜在并发错误的数量。</p>
		<p>
				<a name="N100AD">
						<span class="smalltitle">
								<strong>在恶意运行的代码面前是安全的</strong>
						</span>
				</a>
		</p>
		<p>把对象当作参数的方法不应变更那些对象的状态，除非文档明确说明可以这样做，或者实际上这些方法具有该对象的所有权。当我们将一个对象传递给普通方法时，通常不希望对象返回时已被更改。但是，使用可变对象时，完全会是这样的。如果将 <code><font face="Courier" size="2">java.awt.Point</font></code> 传递给诸如 <code><font face="Courier" size="2">Component.setLocation()</font></code> 的方法，根本不会阻止 <code><font face="Courier" size="2">setLocation</font></code> 修改我们传入的 <code><font face="Courier" size="2">Point</font></code> 的位置，也不会阻止 setLocation 存储对该点的引用并稍后在另一个方法中更改它。（当然， <code><font face="Courier" size="2">Component</font></code> 不这样做，因为它不鲁莽，但是并不是所有类都那么客气。）现在， <code><font face="Courier" size="2">Point</font></code> 的状态已在我们不知道的情况下更改了，其结果具有潜在危险 ― 当点实际上在另一个位置时，我们仍认为它在原来的位置。然而，如果 <code><font face="Courier" size="2">Point</font></code> 是不变的，那么这种恶意的代码就不能以如此令人混乱而危险的方法修改我们的程序状态了。 </p>
		<p>
				<a name="N100D2">
						<span class="smalltitle">
								<strong>良好的键</strong>
						</span>
				</a>
		</p>
		<p>不变对象产生最好的 <code><font face="Courier" size="2">HashMap</font></code> 或 <code><font face="Courier" size="2">HashSet</font></code> 键。有些可变对象根据其状态会更改它们的 <code><font face="Courier" size="2">hashCode()</font></code> 值（如清单 2 中的 <code><font face="Courier" size="2">StringHolder</font></code> 示例类）。如果使用这种可变对象作为 <code><font face="Courier" size="2">HashSet</font></code> 键，然后对象更改了其状态，那么就会对 <code><font face="Courier" size="2">HashSet</font></code> 实现引起混乱 ― 如果枚举集合，该对象仍将出现，但如果用 <code><font face="Courier" size="2">contains()</font></code> 查询集合，它就可能不出现。无需多说，这会引起某些混乱的行为。说明这一情况的清单 2 中的代码将打印“false”、“1”和“moo”。 </p>
		<br />
		<a name="IDAMGW0B">
				<b>清单 2. 可变 StringHolder 类，不适合用作键</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">   public class StringHolder {
        private String string;
        public StringHolder(String s) {
            this.string = s;
        }
        public String getString() {
            return string;
        }
        public void setString(String string) {
            this.string = string;
        }
        public boolean equals(Object o) {
            if (this == o)
                return true;
            else if (o == null || !(o instanceof StringHolder))
                return false;
            else {
                final StringHolder other = (StringHolder) o;
                if (string == null)
                    return (other.string == null);
                else
                    return string.equals(other.string);
            }
        }
        public int hashCode() {
            return (string != null ? string.hashCode() : 0);
        }
        public String toString() {
            return string;
        }
        ...
        StringHolder sh = new StringHolder("blert");
        HashSet h = new HashSet();
        h.add(sh);
        sh.setString("moo");
        System.out.println(h.contains(sh));
        System.out.println(h.size());
        System.out.println(h.iterator().next());
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp02183/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">何时使用不变类</font>
								</strong>
						</span>
				</a>
		</p>
		<p>不变类最适合表示抽象数据类型（如数字、枚举类型或颜色）的值。Java 类库中的基本数字类（如 <code><font face="Courier" size="2">Integer</font></code> 、 <code><font face="Courier" size="2">Long</font></code> 和 <code><font face="Courier" size="2">Float</font></code> ）都是不变的，其它标准数字类型（如 <code><font face="Courier" size="2">BigInteger</font></code> 和 <code><font face="Courier" size="2">BigDecimal</font></code> ）也是不变的。表示复数或精度任意的有理数的类将比较适合于不变性。甚至包含许多离散值的抽象类型（如向量或矩阵）也很适合实现为不变类，这取决于您的应用程序。 </p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="IDAMQY0B">
																				<b>Flyweight 模式</b>
																		</a>
																		<br />
																		<p>不变性启用了 Flyweight 模式，该模式利用共享使得用对象有效地表示大量细颗粒度的对象变得容易。例如，您可能希望用一个对象来表示字处理文档中的每个字符或图像中的每个像素，但这一策略的幼稚实现将会对内存使用和内存管理开销产生高得惊人的花费。Flyweight 模式采用工厂方法来分配对不变的细颗粒度对象的引用，并通过仅使一个对象实例与字母“a”对应来利用共享缩减对象数。有关 Flyweight 模式的更多信息，请参阅经典书籍 <i>Design Patterns</i>（Gamma 等著；请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp02183/#resources"><font color="#996699">参考资料</font></a>）。 </p>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>Java 类库中不变性的另一个不错的示例是 <code><font face="Courier" size="2">java.awt.Color</font></code> 。在某些颜色表示法（如 RGB、HSB 或 CMYK）中，颜色通常表示为一组有序的数字值，但把一种颜色当作颜色空间中的一个特异值，而不是一组有序的独立可寻址的值更有意义，因此将 <code><font face="Courier" size="2">Color</font></code> 作为不变类实现是有道理的。 </p>
		<p>如果要表示的对象是多个基本值的容器（如：点、向量、矩阵或 RGB 颜色），是用可变对象还是用不变对象表示？答案是……要看情况而定。要如何使用它们？它们主要用来表示多维值（如像素的颜色），还是仅仅用作其它对象的一组相关特性集合（如窗口的高度和宽度）的容器？这些特性多久更改一次？如果更改它们，那么各个组件值在应用程序中是否有其自己的含义呢？</p>
		<p>事件是另一个适合用不变类实现的好示例。事件的生命期较短，而且常常会在创建它们的线程以外的线程中消耗，所以使它们成为不变的是利大于弊。大多数 AWT 事件类都没有作为严格的不变类来实现，而是可以有小小的修改。同样地，在使用一定形式的消息传递以在组件间通信的系统中，使消息对象成为不变的或许是明智的。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp02183/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">编写不变类的准则</font>
								</strong>
						</span>
				</a>
		</p>
		<p>编写不变类很容易。如果以下几点都为真，那么类就是不变的：</p>
		<ul>
				<li>它的所有字段都是 final 
</li>
				<li>该类声明为 final 
</li>
				<li>不允许 <code><font face="Courier" size="2">this</font></code> 引用在构造期间转义 
</li>
				<li>任何包含对可变对象（如数组、集合或类似 <code><font face="Courier" size="2">Date</font></code> 的可变类）引用的字段： 
<ul><li>是私有的 
</li><li>从不被返回，也不以其它方式公开给调用程序 
</li><li>是对它们所引用对象的唯一引用 
</li><li>构造后不会更改被引用对象的状态 </li></ul></li>
		</ul>
		<p>最后一组要求似乎挺复杂的，但其基本上意味着如果要存储对数组或其它可变对象的引用，就必须确保您的类对该可变对象拥有独占访问权（因为不然的话，其它类能够更改其状态），而且在构造后您不修改其状态。为允许不变对象存储对数组的引用，这种复杂性是必要的，因为 Java 语言没有办法强制不对 final 数组的元素进行修改。注：如果从传递给构造函数的参数中初始化数组引用或其它可变字段，您必须用防范措施将调用程序提供的参数或您无法确保具有独占访问权的其它信息复制到数组。否则，调用程序会在调用构造函数之后，修改数组的状态。清单 3 显示了编写一个存储调用程序提供的数组的不变对象的构造函数的正确方法（和错误方法）。</p>
		<br />
		<a name="IDADSY0B">
				<b>清单 3. 对不变对象编码的正确和错误方法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class ImmutableArrayHolder {
  private final int[] theArray;
  // Right way to write a constructor -- copy the array
  public ImmutableArrayHolder(int[] anArray) {
    this.theArray = (int[]) anArray.clone();
  }
  // Wrong way to write a constructor -- copy the reference
  // The caller could change the array after the call to the constructor
  public ImmutableArrayHolder(int[] anArray) {
    this.theArray = anArray;
  }
  // Right way to write an accessor -- don't expose the array reference
  public int getArrayLength() { return theArray.length }
  public int getArray(int n)  { return theArray[n]; }
  // Right way to write an accessor -- use clone()
  public int[] getArray()       { return (int[]) theArray.clone(); }
  // Wrong way to write an accessor -- expose the array reference
  // A caller could get the array reference and then change the contents
  public int[] getArray()       { return theArray }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>通过一些其它工作，可以编写使用一些非 final 字段的不变类（例如， <code><font face="Courier" size="2">String</font></code> 的标准实现使用 <code><font face="Courier" size="2">hashCode</font></code> 值的惰性计算），这样可能比严格的 final 类执行得更好。如果类表示抽象类型（如数字类型或颜色）的值，那么您还会想实现 <code><font face="Courier" size="2">hashCode()</font></code> 和 <code><font face="Courier" size="2">equals()</font></code> 方法，这样对象将作为 <code><font face="Courier" size="2">HashMap</font></code> 或 <code><font face="Courier" size="2">HashSet</font></code> 中的一个键工作良好。要保持线程安全，不允许 <code><font face="Courier" size="2">this</font></code> 引用从构造函数中转义是很重要的。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp02183/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">偶尔更改的数据</font>
								</strong>
						</span>
				</a>
		</p>
		<p>有些数据项在程序生命期中一直保持常量，而有些会频繁更改。常量数据显然符合不变性，而状态复杂且频繁更改的对象通常不适合用不变类来实现。那么有时会更改，但更改又不太频繁的数据呢？有什么方法能让 <i>有时</i>更改的数据获得不变性的便利和线程安全的长处呢？ </p>
		<p>
				<code>
						<font face="Courier" size="2">util.concurrent</font>
				</code> 包中的 <code><font face="Courier" size="2">CopyOnWriteArrayList</font></code> 类是如何既利用不变性的能力，又仍允许偶尔修改的一个良好示例。它最适合于支持事件监听程序的类（如用户界面组件）使用。虽然事件监听程序的列表可以更改，但通常它更改的频繁性要比事件的生成少得多。 </p>
		<p>除了在修改列表时， <code><font face="Courier" size="2">CopyOnWriteArrayList</font></code> 并不变更基本数组，而是创建新数组且废弃旧数组之外，它的行为与 <code><font face="Courier" size="2">ArrayList</font></code> 类非常相似。这意味着当调用程序获得迭代器（迭代器在内部保存对基本数组的引用）时，迭代器引用的数组实际上是不变的，从而可以无需同步或冒并发修改的风险进行遍历。这消除了在遍历前克隆列表或在遍历期间对列表进行同步的需要，这两个操作都很麻烦、易于出错，而且完全使性能恶化。如果遍历比插入或除去更加频繁（这在某些情况下是常有的事）， <code><font face="Courier" size="2">CopyOnWriteArrayList</font></code> 会提供更佳的性能和更方便的访问。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp02183/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>使用不变对象比使用可变对象要容易得多。它们只能处于一种状态，所以始终是一致的，它们本来就是线程安全的，可以被自由地共享。使用不变对象可以彻底消除许多容易发生但难以检测的编程错误，如无法在线程间同步访问或在存储对数组或对象的引用前无法克隆该数组或对象。在编写类时，问问自己这个类是否可以作为不变类有效地实现，总是值得的。您可能会对回答常常是肯定的而感到吃惊。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65602.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:50 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65602.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论和实践: 理解 JTS ― 幕后魔术</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65599.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:47:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65599.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65599.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65599.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65599.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65599.html</trackback:ping><description><![CDATA[
		<p>在这个系列的 <a href="http://www.ibm.com/developerworks/library/j-jtp0305.html"><font color="#5c81a7">第 1 部分</font></a>，我们讨论了事务并研究了它们的基本属性 ― 原子性（atomicity）、一致性（consistency）、孤立性（isolation）和持久性（durability）。事务是企业应用程序的基本构件；没有它们，几乎不可能构建有容错能力的企业应用程序。幸运的是，Java 事务服务（Java Transaction Service，JTS）和 J2EE 容器自动为您做了大量的事务管理工作，这样您就不必将事务意识直接集成到组件代码中。结果简直是一种魔术 ― 通过遵守几条简单的规则，J2EE 应用程序就可以自动获得事务性语义，只需极少或根本不需要额外的组件代码。本文旨在通过展示事务管理如何发生，以及发生在何处来揭开这个魔术的神秘面纱。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">什么是 JTS？ </font>
								</strong>
						</span>
				</a>
		</p>
		<p>JTS 是一个 <i>组件事务监视器（component transaction monitor）</i>。这是什么意思？在第 1 部分，我们介绍了 <i>事务处理监视器</i>（TPM）这个概念，TPM 是一个程序，它代表应用程序协调分布式事务的执行。TPM 与数据库出现的时间长短差不多；在 60 年代后期，IBM 首先开发了 CICS，至今人们仍在使用。经典的（或者说 <i>程序化</i>）TPM 管理被程序化定义为针对事务性资源（比如数据库）的操作序列的事务。随着分布式对象协议，如 CORBA、DCOM 和 RMI 的出现，人们希望看到事务更面向对象的前景。将事务性语义告知面向对象的组件要求对 TPM 模型进行扩展 ― 在这个模型中事务是按照事务性对象的调用方法定义的。JTS 只是一个组件事务监视器（有时也称为 <i>对象事务监视器（object transaction monitor）</i>），或称为 CTM。 </p>
		<p>JTS 和 J2EE 的事务支持设计受 CORBA 对象事务服务（CORBA Object Transaction Service，OTS）的影响很大。实际上，JTS 实现 OTS 并充当 Java 事务 API（Java Transaction API）― 一种用来定义事务边界的低级 API ― 和 OTS 之间的接口。使用 OTS 代替创建一个新对象事务协议遵循了现有标准，并使 J2EE 和 CORBA 能够互相兼容。</p>
		<p>乍一看，从程序化事务监视器到 CTM 的转变好像只是术语名称改变了一下。然而，差别不止这一点。当 CTM 中的事务提交或回滚时，与事务相关的对象所做的全部更改都一起被提交或取消。但 CTM 怎么知道对象在事务期间做了什么事？象 EJB 组件之类的事务性组件并没有 <code><font face="Courier" size="2">commit()</font></code> 或 <code><font face="Courier" size="2">rollback()</font></code> 方法，它们也没向事务监视器注册自己做了什么事。那么 J2EE 组件执行的操作如何变成事务的一部分呢？ </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">透明的资源征用 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>当应用程序状态被组件操纵时，它仍然存储在事务性资源管理器（例如，数据库和消息队列服务器）中，这些事务性资源管理器可以注册为分布式事务中的资源管理器。在第 1 部分中，我们讨论了如何在单个事务中征用多个资源管理器，事务管理器如何协调这些资源管理器。资源管理器知道如何把应用程序状态中的变化与特定的事务关联起来。</p>
		<p>但这只是把问题的焦点从组件转移到了资源管理器 ― 容器如何断定什么资源与该事务有关，可以供它征用？请考虑下面的代码，在典型的 EJB 会话 bean 中您可能会发现这样的代码：</p>
		<br />
		<a name="code1">
				<b>清单 1. bean 管理的事务的透明资源征用</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  InitialContext ic = new InitialContext();
  UserTransaction ut = ejbContext.getUserTransaction();
  ut.begin();
  DataSource db1 = (DataSource) ic.lookup("java:comp/env/OrdersDB");
  DataSource db2 = (DataSource) ic.lookup("java:comp/env/InventoryDB");
  Connection con1 = db1.getConnection();
  Connection con2 = db2.getConnection();
  // perform updates to OrdersDB using connection con1
  // perform updates to InventoryDB using connection con2
  ut.commit();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>注意，这个示例中没有征用当前事务中 JDBC 连接的代码 ― 容器会为我们完成这个任务。我们来看一下它是如何发生的。</p>
		<p>
				<a name="3">
						<span class="smalltitle">
								<strong>资源管理器的三种类型 </strong>
						</span>
				</a>
		</p>
		<p>当一个 EJB 组件想访问数据库、消息队列服务器或者其它一些事务性资源时，它需要到资源管理器的连接（通常是使用 JNDI）。而且，J2EE 规范只认可三种类型的事务性资源 ― JDBC 数据库、JMS 消息队列服务器和“其它通过 JCA 访问的事务性服务”。后面一种服务（比如 ERP 系统）必须通过 JCA（J2EE Connector Architecture，J2EE 连接器体系结构）访问。对于这些类型资源中的每一种，容器或提供者都会帮我们把资源征调到事务中。</p>
		<p>在清单 1 中， <code><font face="Courier" size="2">con1</font></code> 和 <code><font face="Courier" size="2">con2</font></code> 好象是普通的 JDBC 连接，比如那些从 <code><font face="Courier" size="2">DriverManager.getConnection()</font></code> 返回的连接。我们从一个 JDBC <code><font face="Courier" size="2">DataSource</font></code> 得到这些连接，JDBC <code><font face="Courier" size="2">DataSource</font></code> 可以通过查找 JNDI 中的数据源名称得到。EJB 组件中被用来查找数据源（ <code><font face="Courier" size="2">java:comp/env/OrdersDB</font></code> ）的名称是特定于组件的；组件的部署描述符的 <code><font face="Courier" size="2">resource-ref</font></code> 部分将其映射为容器管理的一些应用程序级 <code><font face="Courier" size="2">DataSource</font></code> 的 JNDI 名称。 </p>
		<p>
				<a name="4">
						<span class="smalltitle">
								<strong>隐藏的 JDBC 驱动器 </strong>
						</span>
				</a>
		</p>
		<p>每个 J2EE 容器都可以创建有事务意识的池态 <code><font face="Courier" size="2">DataSource</font></code> 对象，但 J2EE 规范并不向您展示如何创建，因为这不在 J2EE 规范内。浏览 J2EE 文档时，您找不到任何关于如何创建 JDBC 数据源的内容。相反，您不得不为您的容器查阅该文档。创建一个数据源可能需要向属性或配置文件添加一个数据源定义，或者也可以通过 GUI 管理工具完成，这取决于您的容器。 </p>
		<p>每个容器（或连接池管理器，如 PoolMan）都提供它自己的创建 <code><font face="Courier" size="2">DataSource</font></code> 机制，JTA 魔术就隐藏在这个机制中。连接池管理器从指定的 JDBC 驱动器得到一个 <code><font face="Courier" size="2">Connection</font></code> ，但在将它返回到应用程序之前，将它与一个也实现 <code><font face="Courier" size="2">Connection</font></code> 的虚包包在一起，将自己置入应用程序和底层连接之间。当创建连接或者执行 JDBC 操作时，包装器询问事务管理器当前线程是不是正在事务的上下文中执行，如果事务中有 <code><font face="Courier" size="2">Connection</font></code> 的话，就自动征用它。 </p>
		<p>其它类型的事务性资源，JMS 消息队列和 JCA 连接器，依靠相似的机制将资源征用隐藏起来，使用户看不到。如果要使 JMS 队列在部署时对 J2EE 应用程序可用，您就要再次使用特定于提供者的机制来创建受管 JMS 对象（队列连接工厂和目标），然后在 JNDI 名称空间内发布这些对象。提供者创建的受管对象包含与 JDBC 包装器（由容器提供的连接池管理器添加）相似的自动征用代码。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">透明的事务控制 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>两种类型的 J2EE 事务 ― 容器管理的和 bean 管理的 ― 在如何启动和结束事务上是不同的。事务启动和结束的地方被称为 <i>事务划分（transaction demarcation）</i>。清单 1 中的示例代码演示了 bean 管理的事务（有时也称为 <i>编程（programmatic）</i>事务）。Bean 管理的事务是由组件使用 <code><font face="Courier" size="2">UserTransaction</font></code> 类显式启动和结束的。通过 <code><font face="Courier" size="2">ejbContext</font></code> 使 <code><font face="Courier" size="2">UserTransaction</font></code> 对 EJB 组件可用，通过 JNDI 使其对其它 J2EE 组件可用。 </p>
		<p>容器根据组件的部署描述符中的事务属性代表应用程序透明地启动和结束容器管理的事务（或称为 <i>宣告式事务（declarative transaction）</i>）。通过将 <code><font face="Courier" size="2">transaction-type</font></code> 属性设置为 <code><font face="Courier" size="2">Container</font></code> 或 <code><font face="Courier" size="2">Bean</font></code> 您可以指出 EJB 组件是使用 bean 管理的事务性支持还是容器管理的事务性支持。 </p>
		<p>使用容器管理的事务，您可以在 EJB 类或方法级别上指定事务性属性；您可以为 EJB 类指定缺省的事务性属性，如果不同的方法会有不同的事务性语义，您还可以为每个方法指定属性。这些事务性属性在装配描述符（assembly descriptor）的 <code><font face="Courier" size="2">container-transaction</font></code> 部分被指定。清单 2 显示了一个装配描述符示例。 <code><font face="Courier" size="2">trans-attribute</font></code> 的受支持的值有： </p>
		<ul>
				<li>
						<code>
								<font face="Courier" size="2">Supports</font>
						</code>
				</li>
				<li>
						<code>
								<font face="Courier" size="2">Required</font>
						</code>
				</li>
				<li>
						<code>
								<font face="Courier" size="2">RequiresNew</font>
						</code>
				</li>
				<li>
						<code>
								<font face="Courier" size="2">Mandatory</font>
						</code>
				</li>
				<li>
						<code>
								<font face="Courier" size="2">NotSupported</font>
						</code>
				</li>
				<li>
						<code>
								<font face="Courier" size="2">Never</font>
						</code>
				</li>
		</ul>
		<p>
				<code>
						<font face="Courier" size="2">trans-attribute</font>
				</code> 决定方法是否支持在事务内部执行、当在事务内部调用方法时容器会执行什么操作以及在事务外部调用方法时容器会执行什么操作。最常用的容器管理的事务属性是 <code><font face="Courier" size="2">Required</font></code> 。如果设置了 <code><font face="Courier" size="2">Required</font></code> ，过程中的事务将在该事务中征用您的 bean，但如果没有正在运行的事务，容器将为您启动一个。在这个系列的第 3 部分，当您可能想使用每个事务属性时，我们将研究各个事务属性之间的区别。 </p>
		<br />
		<a name="code2">
				<b>清单 2. EJB 装配描述符样本</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">&lt;assembly-descriptor&gt;
  ...
  &lt;container-transaction&gt;
    &lt;method&gt;
      &lt;ejb-name&gt;MyBean&lt;/ejb-name&gt;
      &lt;method-name&gt;*&lt;/method-name&gt;
    &lt;/method&gt;
    &lt;trans-attribute&gt;Required&lt;/trans-attribute&gt;
  &lt;/container-transaction&gt;
  &lt;container-transaction&gt;
    &lt;method&gt;
      &lt;ejb-name&gt;MyBean&lt;/ejb-name&gt;
      &lt;method-name&gt;updateName&lt;/method-name&gt;
      &lt;/method&gt;
   &lt;trans-attribute&gt;RequiresNew&lt;/trans-attribute&gt;
  &lt;/container-transaction&gt;
  ...
&lt;/assembly-descriptor&gt;
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="6">
						<span class="smalltitle">
								<strong>功能强大，但很危险 </strong>
						</span>
				</a>
		</p>
		<p>与清单 1 中的示例不同，由于有宣告式事务划分，这段组件代码中没有事务管理代码。这不仅使结果组件代码更加易读（因为它不与事务管理代码混在一起），而且它还有另一个更重要的优点 ― 不必修改，甚至不必访问组件的源代码，就可以在应用程序装配时改变组件的事务性语义。</p>
		<p>尽管能够指定与代码分开的事务划分是一种非常强大的功能，但在装配时做出不好的决定会使应用程序变得不稳定，或者严重影响它的性能。对容器管理的事务进行正确分界的责任由组件开发者和应用程序装配人员共同担当。组件开发者需要提供足够的文档说明组件是做什么的，这样应用程序部署者就能够明智地决定如何构建应用程序的事务。应用程序装配人员需要理解应用程序中的组件是怎样相互作用的，这样就可以用一种既强制应用程序保持一致又不削弱性能的方法对事务进行分界。在这个系列的第 3 部分中我们将讨论这些问题。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="7">
						<span class="atitle">
								<strong>
										<font size="4">透明的事务传播 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>在任何类型的事务中，资源征用都是透明的；容器自动将事务过程中使用的任意事务性资源征调到当前事务中。这个过程不仅扩展到事务性方法使用的资源（比如在清单 1 中获得的数据库连接），还扩展到它调用的方法（甚至远程方法）使用的资源。我们来看一下这是如何发生的。</p>
		<p>
				<a name="8">
						<span class="smalltitle">
								<strong>容器用线程与事务相关联 </strong>
						</span>
				</a>
		</p>
		<p>我们假设对象 <code><font face="Courier" size="2">A</font></code> 的 <code><font face="Courier" size="2">methodA()</font></code> 启动一个事务，然后调用对象 <code><font face="Courier" size="2">B</font></code> 的 <code><font face="Courier" size="2">methodB()</font></code> （对象 B 将得到一个 JDBC 连接并更新数据库）。 <code><font face="Courier" size="2">B</font></code> 获得的连接将被自动征调到 <code><font face="Courier" size="2">A</font></code> 创建的事务中。容器怎么知道要做这件事？ </p>
		<p>当事务启动时，事务上下文与执行线程关联在一起。当 <code><font face="Courier" size="2">A</font></code> 创建事务时， <code><font face="Courier" size="2">A</font></code> 在其中执行的线程与该事务关联在一起。由于本地方法调用与主调程序（caller）在同一个线程内执行，所以 <code><font face="Courier" size="2">A</font></code> 调用的每个方法也都在该事务的上下文中。 </p>
		<p>
				<a name="9">
						<span class="smalltitle">
								<strong>橱中骸骨 </strong>
						</span>
				</a>
		</p>
		<p>如果对象 <code><font face="Courier" size="2">B</font></code> 其实是在另一个线程，甚至另一个 JVM 中执行的 EJB 组件的存根，情况会怎样？令人吃惊的是，远程对象 <code><font face="Courier" size="2">B</font></code> 访问的资源仍将在当前事务中被征用。EJB 对象存根（在主调程序的上下文中执行的那部分）、EJB 协议（IIOP 上的 RMI）和远端的骨架对象协力要使其透明地发生。存根确定调用者是不是正在执行一个事务。如果是，事务标识，或者说 <i>Xid</i>，被作为 IIOP 调用的一部分与方法参数一起传播到远程对象。（IIOP 是 CORBA 远程-调用协议，它为传播执行上下文（比如事务上下文和安全性上下文）的各种元素而备；关于 RMI over IIOP 的更多信息，请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#resources"><font color="#996699">参考资料</font></a>。）如果调用是事务的一部分，那么远程系统上的骨架对象自动设置远程线程的事务上下文，这样，当调用实际的远程方法时，它已经是事务的一部分了。（存根和骨架对象还负责开始和提交容器管理的事务。） </p>
		<p>事务可以由任何 J2EE 组件来启动 ― 一个 EJB 组件、一个 servlet 或者一个 JSP 页面（如果容器支持的话，还可以是一个应用程序客户机）。这意味着，应用程序可以在请求到达时在 servlet 或者 JSP 页面中启动事务、在 servlet 或者 JSP 页面中执行一些处理、作为页面逻辑的一部分访问多个服务器上的实体 bean 和会话 bean 并使所有这些工作透明地成为一个事务的一部分。图 1 演示了事务上下文怎样遵守从 servlet 到 EJB，再到 EJB 的执行路径。</p>
		<br />
		<a name="figure1">
				<b>图 1.单个事务中的多个组件 </b>
		</a>
		<br />
		<img alt="单个事务中的多个组件" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/jta2-1.gif" />
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="10">
						<span class="atitle">
								<strong>
										<font size="4">最优化 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>让容器来管理事务允许容器为我们做出某些最优化决定。在图 1 中，我们看到一个 servlet 和多个 EJB 组件在单个事务的上下文中访问一个数据库。每个组件都获得到数据库的 <code><font face="Courier" size="2">Connection</font></code> ；很可能每个组件都在访问同一个数据库。即使多个连接是从不同的组件到同一个资源，JTS 也可以检测出多个资源是否和事务有关，并最优化该事务的执行。您可以从第 1 部分回忆起来，单个事务要包含多个资源管理器需要使用两阶段提交协议，这比单个资源管理器使用的单阶段提交代价要高。JTS 能够确定事务中是不是只征用了一个资源管理器。如果它检测出所有与事务相关的资源都一样，它可以跳过两阶段提交并让资源管理器自己来处理事务。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp/part2/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="11">
						<span class="atitle">
								<strong>
										<font size="4">结束语 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>这个虑及透明事务控制、资源征用和透明传播的魔术不是 JTS 的一部分，而是 J2EE 容器如何在幕后代表 J2EE 应用程序使用 JTA 和 JTS 服务的一部分。在幕后有许多实体合力使这个魔术透明地发生；EJB 存根和骨架、容器厂商提供的 JDBC 驱动器包装器、数据库厂商提供的 JDBC 驱动器、JMS 提供器和 JCA 连接器。所有这些实体都与事务管理器进行交互，于是应用程序代码就不必与之交互了。</p>
		<p>在第 3 部分，我们将看一下关于管理 J2EE 上下文中事务的一些实际问题 ― 事务划分和孤立 ― 以及它们对应用程序一致性、稳定性和性能的影响。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65599.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:47 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65599.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>轻松使用线程: 同步不是敌人</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65596.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:44:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65596.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65596.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65596.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65596.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65596.html</trackback:ping><description><![CDATA[
		<blockquote>
				<p>与许多其它的编程语言不同，Java语言规范包括对线程和并发的明确支持。语言本身支持并发，这使得指定和管理共享数据的约束以及跨线程操作的计时变得更简单，但是这没有使得并发编程的复杂性更易于理解。这个三部分的系列文章的目的在于帮助程序员理解用Java 语言进行多线程编程的一些主要问题，特别是线程安全对 Java程序性能的影响。</p>
				<p>请点击文章顶部或底部的 <b>讨论</b>进入由 Brian Goetz 主持的 <a href="http://www.ibm.com/developerworks/java_df.nsf/AllViewTemplate?OpenForm&amp;RestrictToCategory=23"><font color="#5c81a7">“Java线程：技巧、窍门和技术”讨论论坛</font></a>，与本文作者和其他读者交流您对本文或整个多线程的想法。注意该论坛讨论的是使用多线程时遇到的所有问题，而并不限于本文的内容。 </p>
		</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>大多数编程语言的语言规范都不会谈到线程和并发的问题；因为一直以来，这些问题都是留给平台或操作系统去详细说明的。但是，Java 语言规范（JLS）却明确包括一个线程模型，并提供了一些语言元素供开发人员使用以保证他们程序的线程安全。</p>
		<p>对线程的明确支持有利也有弊。它使得我们在写程序时更容易利用线程的功能和便利，但同时也意味着我们不得不注意所写类的线程安全，因为任何类都很有可能被用在一个多线程的环境内。</p>
		<p>许多用户第一次发现他们不得不去理解线程的概念的时候，并不是因为他们在写创建和管理线程的程序，而是因为他们正在用一个本身是多线程的工具或框架。任何用过 Swing GUI 框架或写过小服务程序或 JSP 页的开发人员（不管有没有意识到）都曾经被线程的复杂性困扰过。</p>
		<p>Java 设计师是想创建一种语言，使之能够很好地运行在现代的硬件，包括多处理器系统上。要达到这一目的，管理线程间协调的工作主要推给了软件开发人员；程序员必须指定线程间共享数据的位置。在 Java 程序中，用来管理线程间协调工作的主要工具是 <code><font face="Courier" size="2">synchronized</font></code> 关键字。在缺少同步的情况下，JVM 可以很自由地对不同线程内执行的操作进行计时和排序。在大部分情况下，这正是我们想要的，因为这样可以提高性能，但它也给程序员带来了额外的负担，他们不得不自己识别什么时候这种性能的提高会危及程序的正确性。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">synchronized 真正意味着什么？ </font>
								</strong>
						</span>
				</a>
		</p>
		<p>大部分 Java 程序员对同步的块或方法的理解是完全根据使用互斥（互斥信号量）或定义一个临界段（一个必须原子性地执行的代码块）。虽然 <code><font face="Courier" size="2">synchronized</font></code> 的语义中确实包括互斥和原子性，但在管程进入之前和在管程退出之后发生的事情要复杂得多。 </p>
		<p>
				<code>
						<font face="Courier" size="2">synchronized</font>
				</code> 的语义确实保证了一次只有一个线程可以访问被保护的区段，但同时还包括同步线程在主存内互相作用的规则。理解 Java 内存模型（JMM）的一个好方法就是把各个线程想像成运行在相互分离的处理器上，所有的处理器存取同一块主存空间，每个处理器有自己的缓存，但这些缓存可能并不总和主存同步。在缺少同步的情况下，JMM 会允许两个线程在同一个内存地址上看到不同的值。而当用一个管程（锁）进行同步的时候，一旦申请加了锁，JMM 就会马上要求该缓存失效，然后在它被释放前对它进行刷新（把修改过的内存位置写回主存）。不难看出为什么同步会对程序的性能影响这么大；频繁地刷新缓存代价会很大。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">使用一条好的运行路线</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果同步不适当，后果是很严重的：会造成数据混乱和争用情况，导致程序崩溃，产生不正确的结果，或者是不可预计的运行。更糟的是，这些情况可能很少发生且具有偶然性（使得问题很难被监测和重现）。如果测试环境和开发环境有很大的不同，无论是配置的不同，还是负荷的不同，都有可能使得这些问题在测试环境中根本不出现，从而得出错误的结论：我们的程序是正确的，而事实上这些问题只是还没出现而已。</p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="N1008A">
																				<b>争用情况定义</b>
																		</a>
																		<br />
																		<p>
																				<i>争用情况</i>是一种特定的情况：两个或更多的线程或进程读或写一些共享数据，而最终结果取决于这些线程是如何被调度计时的。争用情况可能会导致不可预见的结果和隐蔽的程序错误。 </p>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>另一方面，不当或过度地使用同步会导致其它问题，比如性能很差和死锁。当然，性能差虽然不如数据混乱那么严重，但也是一个严重的问题，因此同样不可忽视。编写优秀的多线程程序需要使用好的运行路线，足够的同步可以使您的数据不发生混乱，但不需要滥用到去承担死锁或不必要地削弱程序性能的风险。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">同步的代价有多大？</font>
								</strong>
						</span>
				</a>
		</p>
		<p>由于包括缓存刷新和设置失效的过程，Java 语言中的同步块通常比许多平台提供的临界段设备代价更大，这些临界段通常是用一个原子性的“test and set bit”机器指令实现的。即使一个程序只包括一个在单一处理器上运行的单线程，一个同步的方法调用仍要比非同步的方法调用慢。如果同步时还发生锁定争用，那么性能上付出的代价会大得多，因为会需要几个线程切换和系统调用。</p>
		<p>幸运的是，随着每一版的 JVM 的不断改进，既提高了 Java 程序的总体性能，同时也相对减少了同步的代价，并且将来还可能会有进一步的改进。此外，同步的性能代价经常是被夸大的。一个著名的资料来源就曾经引证说一个同步的方法调用比一个非同步的方法调用慢 50 倍。虽然这句话有可能是真的，但也会产生误导，而且已经导致了许多开发人员即使在需要的时候也避免使用同步。</p>
		<p>严格依照百分比计算同步的性能损失并没有多大意义，因为一个无争用的同步给一个块或方法带来的是固定的性能损失。而这一固定的延迟带来的性能损失百分比取决于在该同步块内做了多少工作。对一个 <i>空</i>方法的同步调用可能要比对一个空方法的非同步调用慢 20 倍，但我们多长时间才调用一次空方法呢？当我们用更有代表性的小方法来衡量同步损失时，百分数很快就下降到可以容忍的范围之内。 </p>
		<p>表 1 把一些这种数据放在一起来看。它列举了一些不同的实例，不同的平台和不同的 JVM 下一个同步的方法调用相对于一个非同步的方法调用的损失。在每一个实例下，我运行一个简单的程序，测定循环调用一个方法 10，000，000 次所需的运行时间，我调用了同步和非同步两个版本，并比较了结果。表格中的数据是同步版本的运行时间相对于非同步版本的运行时间的比率；它显示了同步的性能损失。每次运行调用的都是清单 1 中的简单方法之一。</p>
		<p>表格 1 中显示了同步方法调用相对于非同步方法调用的相对性能；为了用绝对的标准测定性能损失，必须考虑到 JVM 速度提高的因素，这并没有在数据中体现出来。在大多数测试中，每个 JVM 的更高版本都会使 JVM 的总体性能得到很大提高，很有可能 1.4 版的 Java 虚拟机发行的时候，它的性能还会有进一步的提高。</p>
		<p>
				<a name=" ">
						<span class="smalltitle">
								<strong>表 1. 无争用同步的性能损失</strong>
						</span>
				</a>
		</p>
		<p>
		</p>
		<table cellspacing="1" cellpadding="1" border="1">
				<tbody>
						<tr>
								<td>
										<strong>JDK</strong>
								</td>
								<td>
										<b>staticEmpty</b>
								</td>
								<td>
										<b>empty</b>
								</td>
								<td>
										<b>fetch</b>
								</td>
								<td>
										<b>hashmapGet</b>
								</td>
								<td>
										<b>singleton</b>
								</td>
								<td>
										<b>create</b>
								</td>
						</tr>
						<tr>
								<td>Linux / JDK 1.1</td>
								<td>9.2</td>
								<td>2.4</td>
								<td>2.5</td>
								<td>n/a</td>
								<td>2.0</td>
								<td>1.42</td>
						</tr>
						<tr>
								<td>Linux / IBM Java SDK 1.1</td>
								<td>33.9</td>
								<td>18.4</td>
								<td>14.1</td>
								<td>n/a</td>
								<td>6.9</td>
								<td>1.2</td>
						</tr>
						<tr>
								<td>Linux / JDK 1.2</td>
								<td>2.5</td>
								<td>2.2</td>
								<td>2.2</td>
								<td>1.64</td>
								<td>2.2</td>
								<td>1.4</td>
						</tr>
						<tr>
								<td>Linux / JDK 1.3 (no JIT)</td>
								<td>2.52</td>
								<td>2.58</td>
								<td>2.02</td>
								<td>1.44</td>
								<td>1.4</td>
								<td>1.1</td>
						</tr>
						<tr>
								<td>Linux / JDK 1.3 -server</td>
								<td>28.9</td>
								<td>21.0</td>
								<td>39.0</td>
								<td>1.87</td>
								<td>9.0</td>
								<td>2.3</td>
						</tr>
						<tr>
								<td>Linux / JDK 1.3 -client</td>
								<td>21.2</td>
								<td>4.2</td>
								<td>4.3</td>
								<td>1.7</td>
								<td>5.2</td>
								<td>2.1</td>
						</tr>
						<tr>
								<td>Linux / IBM Java SDK 1.3</td>
								<td>8.2</td>
								<td>33.4</td>
								<td>33.4</td>
								<td>1.7</td>
								<td>20.7</td>
								<td>35.3</td>
						</tr>
						<tr>
								<td>Linux / gcj 3.0</td>
								<td>2.1</td>
								<td>3.6</td>
								<td>3.3</td>
								<td>1.2</td>
								<td>2.4</td>
								<td>2.1</td>
						</tr>
						<tr>
								<td>Solaris / JDK 1.1</td>
								<td>38.6</td>
								<td>20.1</td>
								<td>12.8</td>
								<td>n/a</td>
								<td>11.8</td>
								<td>2.1</td>
						</tr>
						<tr>
								<td>Solaris / JDK 1.2</td>
								<td>39.2</td>
								<td>8.6</td>
								<td>5.0</td>
								<td>1.4</td>
								<td>3.1</td>
								<td>3.1</td>
						</tr>
						<tr>
								<td>Solaris / JDK 1.3 (no JIT)</td>
								<td>2.0</td>
								<td>1.8</td>
								<td>1.8</td>
								<td>1.0</td>
								<td>1.2</td>
								<td>1.1</td>
						</tr>
						<tr>
								<td>Solaris / JDK 1.3 -client</td>
								<td>19.8</td>
								<td>1.5</td>
								<td>1.1</td>
								<td>1.3</td>
								<td>2.1</td>
								<td>1.7</td>
						</tr>
						<tr>
								<td>Solaris / JDK 1.3 -server</td>
								<td>1.8</td>
								<td>2.3</td>
								<td>53.0</td>
								<td>1.3</td>
								<td>4.2</td>
								<td>3.2</td>
						</tr>
				</tbody>
		</table>
		<br />
		<a name="N10226">
				<b>清单 1. 基准测试中用到的简单方法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> public static void staticEmpty() {  }

  public void empty() {  }

  public Object fetch() { return field; }

  public Object singleton() {
    if (singletonField == null)
      singletonField = new Object();
    return singletonField;
  }

  public Object hashmapGet() {
    return hashMap.get("this");
  }

  public Object create() { 
    return new Object();
  }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这些小基准测试也阐明了存在动态编译器的情况下解释性能结果所面临的挑战。对于 1.3 JDK 在有和没有 JIT 时，数字上的巨大差异需要给出一些解释。对那些非常简单的方法（ <code><font face="Courier" size="2">empty</font></code> 和 <code><font face="Courier" size="2">fetch</font></code> ），基准测试的本质（它只是执行一个几乎什么也不做的紧凑的循环）使得 JIT 可以动态地编译整个循环，把运行时间压缩到几乎没有的地步。但在一个实际的程序中，JIT 能否这样做就要取决于很多因素了，所以，无 JIT 的计时数据可能在做公平对比时更有用一些。在任何情况下，对于更充实的方法（ <code><font face="Courier" size="2">create</font></code> 和 <code><font face="Courier" size="2">hashmapGet</font></code> ），JIT 就不能象对更简单些的方法那样使非同步的情况得到巨大的改进。另外，从数据中看不出 JVM 是否能够对测试的重要部分进行优化。同样，在可比较的 IBM 和 Sun JDK 之间的差异反映了 IBM Java SDK 可以更大程度地优化非同步的循环，而不是同步版本代价更高。这在纯计时数据中可以明显地看出（这里不提供）。 </p>
		<p>从这些数字中我们可以得出以下结论：对非争用同步而言，虽然存在性能损失，但在运行许多不是特别微小的方法时，损失可以降到一个合理的水平；大多数情况下损失大概在 10% 到 200% 之间（这是一个相对较小的数目）。所以，虽然同步每个方法是不明智的（这也会增加死锁的可能性），但我们也不需要这么害怕同步。这里使用的简单测试是说明一个无争用同步的代价要比创建一个对象或查找一个 <code><font face="Courier" size="2">HashMap</font></code> 的代价小。 </p>
		<p>由于早期的书籍和文章暗示了无争用同步要付出巨大的性能代价，许多程序员就竭尽全力避免同步。这种恐惧导致了许多有问题的技术出现，比如说 double-checked locking（DCL）。许多关于 Java 编程的书和文章都推荐 DCL，它看上去真是避免不必要的同步的一种聪明的方法，但实际上它根本没有用，应该避免使用它。DCL 无效的原因很复杂，已超出了本文讨论的范围（要深入了解，请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#resources"><font color="#996699">参考资料</font></a>里的链接）。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">不要争用</font>
								</strong>
						</span>
				</a>
		</p>
		<p>假设同步使用正确，若线程真正参与争用加锁，您也能感受到同步对实际性能的影响。并且无争用同步和争用同步间的性能损失差别很大；一个简单的测试程序指出争用同步比无争用同步慢 50 倍。把这一事实和我们上面抽取的观察数据结合在一起，可以看出使用一个争用同步的代价至少相当于创建 50 个对象。</p>
		<p>所以，在调试应用程序中同步的使用时，我们应该努力减少实际争用的数目，而根本不是简单地试图避免使用同步。这个系列的第 2 部分将把重点放在减少争用的技术上，包括减小锁的粒度、减小同步块的大小以及减小线程间共享数据的数量。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">什么时候需要同步？</font>
								</strong>
						</span>
				</a>
		</p>
		<p>要使您的程序线程安全，首先必须确定哪些数据将在线程间共享。如果正在写的数据以后可能被另一个线程读到，或者正在读的数据可能已经被另一个线程写过了，那么这些数据就是共享数据，必须进行同步存取。有些程序员可能会惊讶地发现，这些规则在简单地检查一个共享引用是否非空的时候也用得上。</p>
		<p>许多人会发现这些定义惊人地严格。有一种普遍的观点是，如果只是要读一个对象的字段，不需要请求加锁，尤其是在 JLS 保证了 32 位读操作的原子性的情况下，它更是如此。但不幸的是，这个观点是错误的。除非所指的字段被声明为 <code><font face="Courier" size="2">volatile</font></code> ，否则 JMM 不会要求下面的平台提供处理器间的缓存一致性和顺序连贯性，所以很有可能，在某些平台上，没有同步就会读到陈旧的数据。有关更详细的信息，请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#resources"><font color="#996699">参考资料</font></a>。 </p>
		<p>在确定了要共享的数据之后，还要确定要如何保护那些数据。在简单情况下，只需把它们声明为 <code><font face="Courier" size="2">volatile</font></code> 即可保护数据字段；在其它情况下，必须在读或写共享数据前请求加锁，一个很好的经验是明确指出使用什么锁来保护给定的字段或对象，并在你的代码里把它记录下来。 </p>
		<p>还有一点值得注意的是，简单地同步存取器方法（或声明下层的字段为 <code><font face="Courier" size="2">volatile</font></code> ）可能并不足以保护一个共享字段。可以考虑下面的示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> ...
  private int foo;
  public synchronized int getFoo() { return foo; } 
  public synchronized void setFoo(int f) { foo = f; }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果一个调用者想要增加 <code><font face="Courier" size="2">foo</font></code> 属性值，以下完成该功能的代码就不是线程安全的： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> ...
  setFoo(getFoo() + 1);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果两个线程试图同时增加 <code><font face="Courier" size="2">foo</font></code> 属性值，结果可能是 <code><font face="Courier" size="2">foo</font></code> 的值增加了 1 或 2，这由计时决定。调用者将需要同步一个锁，才能防止这种争用情况；一个好方法是在 JavaDoc 类中指定同步哪个锁，这样类的调用者就不需要自己猜了。 </p>
		<p>以上情况是一个很好的示例，说明我们应该注意多层次粒度的数据完整性；同步存取器方法确保调用者能够存取到一致的和最近版本的属性值，但如果希望属性的将来值与当前值一致，或多个属性间相互一致，我们就必须同步复合操作 ― 可能是在一个粗粒度的锁上。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="6">
						<span class="atitle">
								<strong>
										<font size="4">如果情况不确定，考虑使用同步包装</font>
								</strong>
						</span>
				</a>
		</p>
		<p>有时，在写一个类的时候，我们并不知道它是否要用在一个共享环境里。我们希望我们的类是线程安全的，但我们又不希望给一个总是在单线程环境内使用的类加上同步的负担，而且我们可能也不知道使用这个类时合适的锁粒度是多大。幸运的是，通过提供同步包装，我们可以同时达到以上两个目的。Collections 类就是这种技术的一个很好的示例；它们是非同步的，但在框架中定义的每个接口都有一个同步包装（例如， <code><font face="Courier" size="2">Collections.synchronizedMap()</font></code> ），它用一个同步的版本来包装每个方法。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="7">
						<span class="atitle">
								<strong>
										<font size="4">结论</font>
								</strong>
						</span>
				</a>
		</p>
		<p>虽然 JLS 给了我们可以使我们的程序线程安全的工具，但线程安全也不是天上掉下来的馅饼。使用同步会蒙受性能损失，而同步使用不当又会使我们承担数据混乱、结果不一致或死锁的风险。幸运的是，在过去的几年内 JVM 有了很大的改进，大大减少了与正确使用同步相关的性能损失。通过仔细分析在线程间如何共享数据，适当地同步对共享数据的操作，可以使得您的程序既是线程安全的，又不会承受过多的性能负担。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65596.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:44 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65596.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 做个好的（事件）侦听器</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65595.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:43:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65595.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65595.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65595.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65595.html</trackback:ping><description><![CDATA[
		<blockquote>观察者模式在 Swing 开发中很常见，在 GUI 应用程序以外的场景中，它对于消除组件的耦合性也非常有用。但是，仍然存在一些侦听器登记和调用方面的常见缺陷。在 <i>Java 理论与实践</i> 的这一期中，Java 专家 Brian Goetz 就如何做一个好的侦听器，以及如何对您的侦听器也友好，提供了一些感觉很好的建议。请在相应的 <a href="javascript:void forumWindow()"><font color="#996699">讨论论坛</font></a> 上与作者和其他读者分享您对这篇文章的想法。（您也可以单击本文顶部或底部的 <b>讨论</b> 访问论坛。）</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>Swing 框架以事件侦听器的形式广泛利用了观察者模式（也称为发布-订阅模式）。Swing 组件作为用户交互的目标，在用户与它们交互的时候触发事件；数据模型类在数据发生变化时触发事件。用这种方式使用观察者，可以让控制器与模型分离，让模型与视图分离，从而简化 GUI 应用程序的开发。</p>
		<p>“四人帮”的 <i>设计模式</i> 一书（参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07265/#resources"><font color="#996699">参考资料</font></a>）把观察者模式描述为：定义对象之间的“一对多”关系，这样一个对象改变状态时，所有它的依赖项都会被通知，并自动更新。观察者模式支持组件之间的松散耦合；组件可以保持它们的状态同步，却不需要直接知道彼此的标识或内部情况，从而促进了组件的重用。 </p>
		<p>AWT 和 Swing 组件（例如 <code><font face="Courier" size="2">JButton</font></code> 或 <code><font face="Courier" size="2">JTable</font></code>）使用观察者模式消除了 GUI 事件生成与它们在指定应用程序中的语义之间的耦合。类似地，Swing 的模型类，例如 <code><font face="Courier" size="2">TableModel</font></code> 和 <code><font face="Courier" size="2">TreeModel</font></code>，也使用观察者消除数据模型表示 与视图生成之间的耦合，从而支持相同数据的多个独立的视图。Swing 定义了 <code><font face="Courier" size="2">Event</font></code> 和 <code><font face="Courier" size="2">EventListener</font></code> 对象层次结构；可以生成事件的组件，例如 <code><font face="Courier" size="2">JButton</font></code>（可视组件） 或 <code><font face="Courier" size="2">TableModel</font></code>（数据模型），提供了 <code><font face="Courier" size="2">addXxxListener()</font></code> 和 <code><font face="Courier" size="2">removeXxxListener()</font></code> 方法，用于侦听器的登记和取消登记。这些类负责决定什么时候它们需要触发事件，什么时候确实触发事件，以及什么时候调用所有登记的侦听器。 </p>
		<p>为了支持侦听器，对象需要维护一个已登记的侦听器列表，提供侦听器登记和取消登记的手段，并在适当的事件发生时调用每个侦听器。使用和支持侦听器很容易（不仅仅在 GUI 应用程序中），但是在登记接口的两边（它们是支持侦听器的组件和登记侦听器的组件）都应当避免一些缺陷。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">线程安全问题</font>
								</strong>
						</span>
				</a>
		</p>
		<p>通常，调用侦听器的线程与登记侦听器的线程不同。要支持从不同线程登记侦听器，那么不管用什么机制存储和管理活动侦听器列表，这个机制都必须是线程安全的。Sun 的文档中的许多示例使用 <code><font face="Courier" size="2">Vector</font></code> 保存侦听器列表，它解决了部分问题，但是没有解决全部问题。在事件触发时，触发它的组件会考虑迭代侦听器列表，并调用每个侦听器，这就带来了并发修改的风险，比如在侦听器列表迭代期间，某个线程偶然想添加或删除一个侦听器。 </p>
		<p>
				<a name="1.1">
						<span class="smalltitle">
								<strong>管理侦听器列表</strong>
						</span>
				</a>
		</p>
		<p>假设您使用 <code><font face="Courier" size="2">Vector&lt;Listener&gt;</font></code> 保存侦听器列表。虽然 Vector 类是线程安全的（意味着不需要进行额外的同步就可调用它的方法，没有破坏 <code><font face="Courier" size="2">Vector</font></code> 数据结构的风险），但是集合的迭代中包含“检测然后执行”序列，如果在迭代期间集合被修改，就有了失败的风险。假设迭代开始时列表中有三个侦听器。在迭代 <code><font face="Courier" size="2">Vector</font></code> 时，重复调用 <code><font face="Courier" size="2">size()</font></code> 和 <code><font face="Courier" size="2">get()</font></code> 方法，直到所有元素都检索完，如清单 1 所示： </p>
		<br />
		<a name="N100CB">
				<b>清单 1. Vector 的不安全迭代</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">Vector&lt;Listener&gt; v;
for (int i=0; i&lt;v.size(); i++)
  v.get(i).eventHappened(event);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但是，如果恰好就在最后一次调用 <code><font face="Courier" size="2">Vector.size()</font></code> 之后，有人从列表中删除了一个侦听器，会发生什么呢？现在，<code><font face="Courier" size="2">Vector.get()</font></code> 将返回 <code><font face="Courier" size="2">null</font></code> （这是对的，因为从上次检测 vector 的状态以来，它的状态已经变了），而在试图调用 <code><font face="Courier" size="2">eventHappened()</font></code> 时，会抛出 <code><font face="Courier" size="2">NullPointerException</font></code>。这是“检测然后执行”序列的一个示例 —— 检测是否存在更多元素，如果存在，就取得下一元素 —— 但是在存在并发修改的情况下，检测之后状态可能已经变化。图 1 演示了这个问题： </p>
		<p>
				<a name="N100EB">
						<span class="smalltitle">
								<strong>图 1. 并发迭代和修改，造成意料之外的失败</strong>
						</span>
				</a>
		</p>
		<strong>
				<img height="97" alt="并发迭代和修改，造成意料之外的失败" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp07265/figure1.gif" width="600" />
		</strong>
		<br />
		<p>这个问题的一个解决方案是在迭代期间持有对 Vector 的锁；另一个方案是克隆 Vector 或调用它的 <code><font face="Courier" size="2">toArray()</font></code> 方法，在每次发生事件时检索它的内容。所有这两个方法都有性能上的问题：第一个的风险是在迭代期间，会把其他想访问侦听器列表的线程锁在外面；第二个则要创建临时对象，而且每次事件发生时都要拷贝列表。 </p>
		<p>如果用迭代器（Iterator）去遍历侦听器列表，也会有同样的问题，只是表现略有不同； <code><font face="Courier" size="2">iterator()</font></code> 实现不抛出 <code><font face="Courier" size="2">NullPointerException</font></code>，它在探测到迭代开始之后集合发生修改时，会抛出 <code><font face="Courier" size="2">ConcurrentModificationException</font></code>。同样，也可以通过在迭代期间锁定集合防止这个问题。 </p>
		<p>
				<code>
						<font face="Courier" size="2">java.util.concurrent</font>
				</code> 中的 <code><font face="Courier" size="2">CopyOnWriteArrayList</font></code> 类，能够帮助防止这个问题。它实现了 <code><font face="Courier" size="2">List</font></code>，而且是线程安全的，但是它的迭代器不会抛出 <code><font face="Courier" size="2">ConcurrentModificationException</font></code>，遍历期间也不要求额外的锁定。这种特性组合是通过在每次列表修改时，在内部重新分配并拷贝列表内容而实现的，这样，遍历内容的线程不需要处理变化 —— 从它们的角度来说，列表的内容在遍历期间保持不变。虽然这听起来可能没效率，但是请记住，在多数观察者情况下，每个组件只有少量侦听器，遍历的数量远远超过插入和删除的数量。所以更快的迭代可以补偿较慢的变化过程，并提供更好的并发性，因为多个线程可以同时迭代列表。 </p>
		<p>
				<a name="1.2">
						<span class="smalltitle">
								<strong>初始化的安全风险</strong>
						</span>
				</a>
		</p>
		<p>从侦听器的构造函数中登记它很诱惑人，但是这是一个应当避免的诱惑。它仅会造成“失效侦听器（lapsed listener）的问题（我稍后讨论它），而且还会造成多个线程安全问题。清单 2 显示了一个看起来没什么害处的同时构造和登记侦听器的企图。问题是：它造成到对象的“this”引用在对象完全构造完成之前转义。虽然看起来没什么害处，因为登记是构造函数做的最后一件事，但是看到的东西是有欺骗性的： </p>
		<br />
		<a name="N1012D">
				<b>清单 2. 事件侦听器允许“this”引用转义，造成问题</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class EventListener { 

  public EventListener(EventSource eventSource) {
    // do our initialization
    ...

    // register ourselves with the event source
    eventSource.registerListener(this);
  }

  public onEvent(Event e) { 
    // handle the event
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在继承事件侦听器的时候，会出现这种方法的一个风险：这时，子类构造函数做的任何工作都是在 <code><font face="Courier" size="2">EventListener</font></code> 构造函数运行之后进行的，也就是在 <code><font face="Courier" size="2">EventListener</font></code> 发布之后，所以会造成争用情况。在某些不幸的时候，清单 3 中的 onEvent 方法会在列表字段还没初始化之前就被调用，从而在取消 final 字段的引用时，会生成非常让人困惑的 <code><font face="Courier" size="2">NullPointerException</font></code> 异常： </p>
		<br />
		<a name="N10146">
				<b>清单 3. 继承清单 2 的 EventListener 类造成的问题</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class RecordingEventListener extends EventListener {
  private final ArrayList&lt;Event&gt; list;

  public RecordingEventListener(EventSource eventSource) {
    super(eventSource);
    list = Collections.synchronizedList(new ArrayList&lt;Event&gt;());
  }

  public onEvent(Event e) { 
    list.add(e);
    super.onEvent(e);
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>即使侦听器类是 final 的，不能派生子类，也不应当允许“this”引用在构造函数中转义 —— 这样做会危害 Java 内存模型的某些安全保证。如果“this”这个词不会出现在程序中，就可让“this”引用转义；发布一个非静态内部类实例可以达到相同的效果，因为内部类持有对它包围的对象的“this”引用的引用。偶然地允许“this”引用转义的最常见原因，就是登记侦听器，如清单 4 所示。<b>事件侦听器不应当在构造函数中登记！</b></p>
		<br />
		<a name="N10156">
				<b>清单 4. 通过发布内部类实例，显式地允许“this”引用转义</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class EventListener2 {
  public EventListener2(EventSource eventSource) {

    eventSource.registerListener(
      new EventListener() {
        public void onEvent(Event e) { 
          eventReceived(e);
        }
      });
  }

  public void eventReceived(Event e) {
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="1.2">
						<span class="smalltitle">
								<strong>侦听器线程安全</strong>
						</span>
				</a>
		</p>
		<p>使用侦听器造成的第三个线程安全问题来自这个事实：侦听器可能想访问应用程序数据，而调用侦听器的线程通常不直接在应用程序的控制之下。如果在 <code><font face="Courier" size="2">JButton</font></code> 或其他 Swing 组件上登记侦听器，那么会从 EDT 调用该侦听器。侦听器的代码可以从 EDT 安全地调用 Swing 组件上的方法，但是如果对象本身不是线程安全的，那么从侦听器访问应用程序对象会给应用程序增加新的线程安全需求。 </p>
		<p>Swing 组件生成的事件是用户交互的结果，但是 Swing 模型类是在 <code><font face="Courier" size="2">fireXxxEvent()</font></code> 方法被调用的时候生成事件。这些方法又会在调用它们的线程中调用侦听器。因为 Swing 模型类不是线程安全的，而且假设被限制在 EDT 内，所以对 <code><font face="Courier" size="2">fireXxxEvent()</font></code> 的任何调用也都应当从 EDT 执行。如果想从另外的线程触发事件，那么应当用 Swing 的 <code><font face="Courier" size="2">invokeLater()</font></code> 功能让方法转而在 EDT 内调用。一般来说，要注意调用事件侦听器的线程，还要保证它们涉及的任何对象或者是线程安全的，或者在访问它们的地方，受到适当的同步（或者是 Swing 模型类的线程约束）的保护。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07265/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">失效侦听器</font>
								</strong>
						</span>
				</a>
		</p>
		<p>不管什么时候使用观察者模式，都耦合着两个独立组件 —— 观察者和被观察者，它们通常有不同的生命周期。登记侦听器的后果之一就是：它在被观察对象和侦听器之间建立起很强的引用关系，这种关系防止侦听器（以及它引用的对象）被垃圾收集，直到侦听器取消登记为止。在许多情况下，侦听器的生命周期至少要和被观察的组件一样长 —— 许多侦听器会在整个应用程序期间都存在。但是在某些情况下，应当短期存在的侦听器最后变成了永久的，它们这种无意识的拖延的证据就是应用程序性能变慢、高于必需的内存使用。 </p>
		<p>“失效侦听器”的问题可以由设计级别上的不小心造成：没有恰当地考虑包含的对象的寿命，或者由于松懈的编码。侦听器登记和取消登记应当结对进行。但是即使这么做，也必须保证是在正确的时间执行取消登记。清单 5 显示了会造成失效侦听器的编码习惯的示例。它在组件上登记侦听器，执行某些动作，然后取消登记侦听器： </p>
		<br />
		<a name="N10189">
				<b>清单 5. 有造成失效侦听器风险的代码</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  public void processFile(String filename) throws IOException {
    cancelButton.registerListener(this);
    // open file, read it, process it
    // might throw IOException
    cancelButton.unregisterListener(this);
  }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>清单 5 的问题是：如果文件处理代码抛出了 IOException —— 这是很有可能的 —— 那么侦听器就永远不会取消登记，这就意味着它永远不会被垃圾收集。取消登记的操作应当在 <code><font face="Courier" size="2">finally</font></code> 块中进行，这样，<code><font face="Courier" size="2">processFile()</font></code> 方法的所有出口都会执行它。 </p>
		<p>有时推荐的一个处理失效侦听器的方法是使用弱引用。虽然这种方法可行，但是实现起来很麻烦。要让它工作，需要找到另外一个对象，它的生命周期恰好是侦听器的生命周期，并安排它持有对侦听器的强引用，这可不是件容易的事。 </p>
		<p>另外一项可以用来找到隐藏失效侦听器的技术是：防止指定侦听器对象在指定事件源上登记两次。这种情况通常是 bug 的迹象 —— 侦听器登记了，但是没有取消登记，然后再次登记。不用检测问题，就能缓解这个问题的影响的一种方式是：使用 <code><font face="Courier" size="2">Set</font></code> 代替 <code><font face="Courier" size="2">List</font></code> 来存储侦听器；或者也可以检测 <code><font face="Courier" size="2">List</font></code>，在登记侦听器之前检查是否已经登记了，如果已经登记，就抛出异常（或记录错误），这样就可以搜集编码错误的证据，并采取行动。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07265/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">其他侦听器问题</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在编写侦听器时，应当一直注意它们将要执行的环境。不仅要注意线程安全问题，还需要记住：侦听器也可以用其他方式为它的调用者把事情搞糟。侦听器 <i>不该</i> 做的一件事是：阻塞相当长一段时间（长得可以感觉得到）；调用它的执行上下文很可能希望迅速返回控制。如果侦听器要执行一个可能比较费时的操作，例如处理大型文本，或者要做的工作可能阻塞，例如执行 socket IO，那么侦听器应当把这些操作安排在另一个线程中进行，这样它就可以迅速返回它的调用者。 </p>
		<p>对于不小心的事件源，侦听器会造成麻烦的另一个方式是：抛出未检测的异常。虽然大多数时候，我们不会故意抛出未检测异常，但是确实有些时候会发生这种情况。如果使用清单 1 的方式调用侦听器，列表中的第二个侦听器就会抛出未检测异常，那么不仅后续的侦听器得不到调用（可能造成应用程序处在不一致的状态），而且有可能把执行它的线程破坏掉，从而造成局部应用程序失败。 </p>
		<p>在调用未知代码（侦听器就是这样的代码）时，谨慎的方式是在 <code><font face="Courier" size="2">try-catch</font></code> 块中执行它，这样，行为有误的侦听器不会造成更多不必要的破坏。对于抛出未检测异常的侦听器，您可能想自动对它取消登记，毕竟，抛出未检测异常就证明侦听器坏掉了。（您可能还想记录这个错误或者提醒用户注意，好让用户能够知道为什么程序停止像期望的那样继续工作。）清单 6 显示了这种方式的一个示例，它在迭代循环内部嵌套了 try-catch 块： </p>
		<br />
		<a name="N101C7">
				<b>清单 6. 健壮的侦听器调用</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">List&lt;Listener&gt; list;
for (Iterator&lt;Listener&gt; i=list.iterator; i.hasNext(); ) {
    Listener l = i.next();
    try {
        l.eventHappened(event);
    }
    catch (RuntimeException e) {
        log("Unexpected exception in listener", e);
        i.remove();
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07265/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>观察者模式对于创建松散耦合的组件、鼓励组件重用非常有用，但是它有一些风险，侦听器的编写者和组件的编写者都应当注意。在登记侦听器时，应当一直注意侦听器的生命周期。如果侦听器的寿命应当比应用程序的短，那么请确保取消它的登记，这样它就可以被垃圾收集。在编写侦听器和组件时，请注意它包含的线程安全性问题。侦听器涉及的任何对象，都应当是线程安全的，或者是受线程约束的对象（例如 Swing 模型），侦听器应当确定自己正在正确的线程中执行。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:43 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 用弱引用堵住内存泄漏</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65594.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:42:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65594.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65594.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65594.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65594.html</trackback:ping><description><![CDATA[
		<blockquote>虽然用 Java™ 语言编写的程序在理论上是不会出现“内存泄漏”的，但是有时对象在不再作为程序的逻辑状态的一部分之后仍然不被垃圾收集。本月，负责保障应用程序健康的工程师 Brian Goetz 探讨了无意识的对象保留的常见原因，并展示了如何用弱引用堵住泄漏。 </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>要让垃圾收集（GC）回收程序不再使用的对象，对象的<i>逻辑</i> 生命周期（应用程序使用它的时间）和对该对象拥有的引用的<i>实际</i> 生命周期必须是相同的。在大多数时候，好的软件工程技术保证这是自动实现的，不用我们对对象生命周期问题花费过多心思。但是偶尔我们会创建一个引用，它在内存中包含对象的时间比我们预期的要长得多，这种情况称为<i>无意识的对象保留（unintentional object retention）</i>。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">全局 Map 造成的内存泄漏</font>
								</strong>
						</span>
				</a>
		</p>
		<p>无意识对象保留最常见的原因是使用 <code><font face="Courier" size="2">Map</font></code> 将元数据与临时对象（transient object）相关联。假定一个对象具有中等生命周期，比分配它的那个方法调用的生命周期长，但是比应用程序的生命周期短，如客户机的套接字连接。需要将一些元数据与这个套接字关联，如生成连接的用户的标识。在创建 <code><font face="Courier" size="2">Socket</font></code> 时是不知道这些信息的，并且不能将数据添加到 <code><font face="Courier" size="2">Socket</font></code> 对象上，因为不能控制 <code><font face="Courier" size="2">Socket</font></code> 类或者它的子类。这时，典型的方法就是在一个全局 <code><font face="Courier" size="2">Map</font></code> 中存储这些信息，如清单 1 中的 <code><font face="Courier" size="2">SocketManager</font></code> 类所示： </p>
		<br />
		<a name="listing1">
				<b>清单 1. 使用一个全局 Map 将元数据关联到一个对象</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class SocketManager {
    private Map&lt;Socket,User&gt; m = new HashMap&lt;Socket,User&gt;();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}

SocketManager socketManager;
...
socketManager.setUser(socket, user);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩，但是除非准确地知道什么时候程序不再需要这个套接字，并记住从 <code><font face="Courier" size="2">Map</font></code> 中删除相应的映射，否则，<code><font face="Courier" size="2">Socket</font></code> 和 <code><font face="Courier" size="2">User</font></code> 对象将会永远留在 <code><font face="Courier" size="2">Map</font></code> 中，远远超过响应了请求和关闭套接字的时间。这会阻止 <code><font face="Courier" size="2">Socket</font></code> 和 <code><font face="Courier" size="2">User</font></code> 对象被垃圾收集，即使应用程序不会再使用它们。这些对象留下来不受控制，很容易造成程序在长时间运行后内存爆满。除了最简单的情况，在几乎所有情况下找出什么时候 <code><font face="Courier" size="2">Socket</font></code> 不再被程序使用是一件很烦人和容易出错的任务，需要人工对内存进行管理。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">找出内存泄漏</font>
								</strong>
						</span>
				</a>
		</p>
		<p>程序有内存泄漏的第一个迹象通常是它抛出一个 <code><font face="Courier" size="2">OutOfMemoryError</font></code>，或者因为频繁的垃圾收集而表现出糟糕的性能。幸运的是，垃圾收集可以提供能够用来诊断内存泄漏的大量信息。如果以 <code><font face="Courier" size="2">-verbose:gc</font></code> 或者 <code><font face="Courier" size="2">-Xloggc</font></code> 选项调用 JVM，那么每次 GC 运行时在控制台上或者日志文件中会打印出一个诊断信息，包括它所花费的时间、当前堆使用情况以及恢复了多少内存。记录 GC 使用情况并不具有干扰性，因此如果需要分析内存问题或者调优垃圾收集器，在生产环境中默认启用 GC 日志是值得的。 </p>
		<p>有工具可以利用 GC 日志输出并以图形方式将它显示出来，JTune 就是这样的一种工具（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#resources"><font color="#996699">参考资料</font></a>）。观察 GC 之后堆大小的图，可以看到程序内存使用的趋势。对于大多数程序来说，可以将内存使用分为两部分：<i>baseline</i> 使用和 <i>current load</i> 使用。对于服务器应用程序，baseline 使用就是应用程序在没有任何负荷、但是已经准备好接受请求时的内存使用，current load 使用是在处理请求过程中使用的、但是在请求处理完成后会释放的内存。只要负荷大体上是恒定的，应用程序通常会很快达到一个稳定的内存使用水平。如果在应用程序已经完成了其初始化并且负荷没有增加的情况下，内存使用持续增加，那么程序就可能在处理前面的请求时保留了生成的对象。 </p>
		<p>清单 2 展示了一个有内存泄漏的程序。<code><font face="Courier" size="2">MapLeaker</font></code> 在线程池中处理任务，并在一个 <code><font face="Courier" size="2">Map</font></code> 中记录每一项任务的状态。不幸的是，在任务完成后它不会删除那一项，因此状态项和任务对象（以及它们的内部状态）会不断地积累。 </p>
		<br />
		<a name="listing2">
				<b>清单 2. 具有基于 Map 的内存泄漏的程序</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map&lt;Task, TaskStatus&gt; taskStatus 
        = Collections.synchronizedMap(new HashMap&lt;Task, TaskStatus&gt;());
    private Random random = new Random();

    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };

    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];

        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }

    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>图 1 显示 <code><font face="Courier" size="2">MapLeaker</font></code> GC 之后应用程序堆大小随着时间的变化图。上升趋势是存在内存泄漏的警示信号。（在真实的应用程序中，坡度不会这么大，但是在收集了足够长时间的 GC 数据后，上升趋势通常会表现得很明显。） </p>
		<br />
		<a name="figure1">
				<b>图 1. 持续上升的内存使用趋势</b>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/heapsize.gif" />
		<br />
		<p>确信有了内存泄漏后，下一步就是找出哪种对象造成了这个问题。所有内存分析器都可以生成按照对象类进行分解的堆快照。有一些很好的商业堆分析工具，但是找出内存泄漏不一定要花钱买这些工具 —— 内置的 <code><font face="Courier" size="2">hprof</font></code> 工具也可完成这项工作。要使用 <code><font face="Courier" size="2">hprof</font></code> 并让它跟踪内存使用，需要以 <code><font face="Courier" size="2">-Xrunhprof:heap=sites</font></code> 选项调用 JVM。 </p>
		<p>清单 3 显示分解了应用程序内存使用的 <code><font face="Courier" size="2">hprof</font></code> 输出的相关部分。（<code><font face="Courier" size="2">hprof</font></code> 工具在应用程序退出时，或者用 <code><font face="Courier" size="2">kill -3</font></code> 或在 Windows 中按 Ctrl+Break 时生成使用分解。）注意两次快照相比，<code><font face="Courier" size="2">Map.Entry</font></code>、<code><font face="Courier" size="2">Task</font></code> 和 <code><font face="Courier" size="2">int[]</font></code> 对象有了显著增加。 </p>
		<p>请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/sidefile1.html"><font color="#5c81a7">清单 3</font></a>。</p>
		<p>清单 4 展示了 <code><font face="Courier" size="2">hprof</font></code> 输出的另一部分，给出了 <code><font face="Courier" size="2">Map.Entry</font></code> 对象的分配点的调用堆栈信息。这个输出告诉我们哪些调用链生成了 <code><font face="Courier" size="2">Map.Entry</font></code> 对象，并带有一些程序分析，找出内存泄漏来源一般来说是相当容易的。 </p>
		<br />
		<a name="listing4">
				<b>清单 4. HPROF 输出，显示 Map.Entry 对象的分配点</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">TRACE 300446:
	java.util.HashMap$Entry.&lt;init&gt;(&lt;Unknown Source&gt;:Unknown line)
	java.util.HashMap.addEntry(&lt;Unknown Source&gt;:Unknown line)
	java.util.HashMap.put(&lt;Unknown Source&gt;:Unknown line)
	java.util.Collections$SynchronizedMap.put(&lt;Unknown Source&gt;:Unknown line)
	com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
	com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">弱引用来救援了</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<code>
						<font face="Courier" size="2">SocketManager</font>
				</code> 的问题是 <code><font face="Courier" size="2">Socket</font></code>-<code><font face="Courier" size="2">User</font></code> 映射的生命周期应当与 <code><font face="Courier" size="2">Socket</font></code> 的生命周期相匹配，但是语言没有提供任何容易的方法实施这项规则。这使得程序不得不使用人工内存管理的老技术。幸运的是，从 JDK 1.2 开始，垃圾收集器提供了一种声明这种对象生命周期依赖性的方法，这样垃圾收集器就可以帮助我们防止这种内存泄漏 —— 利用<i>弱引用</i>。 </p>
		<p>弱引用是对一个对象（称为 <i>referent</i>）的引用的持有者。使用弱引用后，可以维持对 referent 的引用，而不会阻止它被垃圾收集。当垃圾收集器跟踪堆的时候，如果对一个对象的引用只有弱引用，那么这个 referent 就会成为垃圾收集的候选对象，就像没有任何剩余的引用一样，而且所有剩余的弱引用都被<i>清除</i>。（只有弱引用的对象称为<i>弱可及（weakly reachable）</i>。） </p>
		<p>
				<code>
						<font face="Courier" size="2">WeakReference</font>
				</code> 的 referent 是在构造时设置的，在没有被清除之前，可以用 <code><font face="Courier" size="2">get()</font></code> 获取它的值。如果弱引用被清除了（不管是 referent 已经被垃圾收集了，还是有人调用了 <code><font face="Courier" size="2">WeakReference.clear()</font></code>），<code><font face="Courier" size="2">get()</font></code> 会返回 <code><font face="Courier" size="2">null</font></code>。相应地，在使用其结果之前，应当总是检查 <code><font face="Courier" size="2">get()</font></code> 是否返回一个非 null 值，因为 referent 最终总是会被垃圾收集的。 </p>
		<p>用一个普通的（强）引用拷贝一个对象引用时，限制 referent 的生命周期至少与被拷贝的引用的生命周期一样长。如果不小心，那么它可能就与程序的生命周期一样 —— 如果将一个对象放入一个全局集合中的话。另一方面，在创建对一个对象的弱引用时，完全没有扩展 referent 的生命周期，只是在<i>对象仍然存活的时候</i>，保持另一种到达它的方法。 </p>
		<p>弱引用对于构造弱集合最有用，如那些在应用程序的其余部分使用对象期间存储关于这些对象的元数据的集合 —— 这就是 <code><font face="Courier" size="2">SocketManager</font></code> 类所要做的工作。因为这是弱引用最常见的用法，<code><font face="Courier" size="2">WeakHashMap</font></code> 也被添加到 JDK 1.2 的类库中，它对键（而不是对值）使用弱引用。如果在一个普通 <code><font face="Courier" size="2">HashMap</font></code> 中用一个对象作为键，那么这个对象在映射从 <code><font face="Courier" size="2">Map</font></code> 中删除之前不能被回收，<code><font face="Courier" size="2">WeakHashMap</font></code> 使您可以用一个对象作为 <code><font face="Courier" size="2">Map</font></code> 键，同时不会阻止这个对象被垃圾收集。清单 5 给出了 <code><font face="Courier" size="2">WeakHashMap</font></code> 的 <code><font face="Courier" size="2">get()</font></code> 方法的一种可能实现，它展示了弱引用的使用： </p>
		<br />
		<a name="listing5">
				<b>清单 5. WeakReference.get() 的一种可能实现</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class WeakHashMap&lt;K,V&gt; implements Map&lt;K,V&gt; {

    private static class Entry&lt;K,V&gt; extends WeakReference&lt;K&gt; 
      implements Map.Entry&lt;K,V&gt; {
        private V value;
        private final int hash;
        private Entry&lt;K,V&gt; next;
        ...
    }

    public V get(Object key) {
        int hash = getHash(key);
        Entry&lt;K,V&gt; e = getChain(hash);
        while (e != null) {
            K eKey= e.get();
            if (e.hash == hash &amp;&amp; (key == eKey || key.equals(eKey)))
                return e.value;
            e = e.next;
        }
        return null;
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>调用 <code><font face="Courier" size="2">WeakReference.get()</font></code> 时，它返回一个对 referent 的强引用（如果它仍然存活的话），因此不需要担心映射在 <code><font face="Courier" size="2">while</font></code> 循环体中消失，因为强引用会防止它被垃圾收集。<code><font face="Courier" size="2">WeakHashMap</font></code> 的实现展示了弱引用的一种常见用法 —— 一些内部对象扩展 <code><font face="Courier" size="2">WeakReference</font></code>。其原因在下面一节讨论引用队列时会得到解释。</p>
		<p>在向 <code><font face="Courier" size="2">WeakHashMap</font></code> 中添加映射时，请记住映射可能会在以后“脱离”，因为键被垃圾收集了。在这种情况下，<code><font face="Courier" size="2">get()</font></code> 返回 <code><font face="Courier" size="2">null</font></code>，这使得测试 <code><font face="Courier" size="2">get()</font></code> 的返回值是否为 <code><font face="Courier" size="2">null</font></code> 变得比平时更重要了。 </p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>用 WeakHashMap 堵住泄漏</strong>
						</span>
				</a>
		</p>
		<p>在 <code><font face="Courier" size="2">SocketManager</font></code> 中防止泄漏很容易，只要用 <code><font face="Courier" size="2">WeakHashMap</font></code> 代替 <code><font face="Courier" size="2">HashMap</font></code> 就行了，如清单 6 所示。（如果 <code><font face="Courier" size="2">SocketManager</font></code> 需要线程安全，那么可以用 <code><font face="Courier" size="2">Collections.synchronizedMap()</font></code> 包装 <code><font face="Courier" size="2">WeakHashMap</font></code>）。当映射的生命周期必须与键的生命周期联系在一起时，可以使用这种方法。不过，应当小心不滥用这种技术，大多数时候还是应当使用普通的 <code><font face="Courier" size="2">HashMap</font></code> 作为 <code><font face="Courier" size="2">Map</font></code> 的实现。 </p>
		<br />
		<a name="listing6">
				<b>清单 6. 用 WeakHashMap 修复 SocketManager</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class SocketManager {
    private Map&lt;Socket,User&gt; m = new WeakHashMap&lt;Socket,User&gt;();
    
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="3.2">
						<span class="smalltitle">
								<strong>引用队列</strong>
						</span>
				</a>
		</p>
		<p>
				<code>
						<font face="Courier" size="2">WeakHashMap</font>
				</code> 用弱引用承载映射键，这使得应用程序不再使用键对象时它们可以被垃圾收集，<code><font face="Courier" size="2">get()</font></code> 实现可以根据 <code><font face="Courier" size="2">WeakReference.get()</font></code> 是否返回 <code><font face="Courier" size="2">null</font></code> 来区分死的映射和活的映射。但是这只是防止 <code><font face="Courier" size="2">Map</font></code> 的内存消耗在应用程序的生命周期中不断增加所需要做的工作的一半，还需要做一些工作以便在键对象被收集后从 <code><font face="Courier" size="2">Map</font></code> 中删除死项。否则，<code><font face="Courier" size="2">Map</font></code> 会充满对应于死键的项。虽然这对于应用程序是不可见的，但是它仍然会造成应用程序耗尽内存，因为即使键被收集了，<code><font face="Courier" size="2">Map.Entry</font></code> 和值对象也不会被收集。 </p>
		<p>可以通过周期性地扫描 <code><font face="Courier" size="2">Map</font></code>，对每一个弱引用调用 <code><font face="Courier" size="2">get()</font></code>，并在 get() 返回 <code><font face="Courier" size="2">null</font></code> 时删除那个映射而消除死映射。但是如果 <code><font face="Courier" size="2">Map</font></code> 有许多活的项，那么这种方法的效率很低。如果有一种方法可以在弱引用的 referent 被垃圾收集时发出通知就好了，这就是<i>引用队列</i> 的作用。 </p>
		<p>引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的主要方法。弱引用有两个构造函数：一个只取 referent 作为参数，另一个还取引用队列作为参数。如果用关联的引用队列创建弱引用，在 referent 成为 GC 候选对象时，这个引用对象（不是 referent）就在引用清除后<i>加入</i> 到引用队列中。之后，应用程序从引用队列提取引用并了解到它的 referent 已被收集，因此可以进行相应的清理活动，如去掉已不在弱集合中的对象的项。（引用队列提供了与 <code><font face="Courier" size="2">BlockingQueue</font></code> 同样的出列模式 —— polled、timed blocking 和 untimed blocking。） </p>
		<p>
				<code>
						<font face="Courier" size="2">WeakHashMap</font>
				</code> 有一个名为 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 的私有方法，大多数 <code><font face="Courier" size="2">Map</font></code> 操作中会调用它，它去掉引用队列中所有失效的引用，并删除关联的映射。清单 7 展示了 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 的一种可能实现。用于存储键-值映射的 <code><font face="Courier" size="2">Entry</font></code> 类型扩展了 <code><font face="Courier" size="2">WeakReference</font></code>，因此当 <code><font face="Courier" size="2">expungeStaleEntries()</font></code> 要求下一个失效的弱引用时，它得到一个 <code><font face="Courier" size="2">Entry</font></code>。用引用队列代替定期扫描内容的方法来清理 <code><font face="Courier" size="2">Map</font></code> 更有效，因为清理过程不会触及活的项，只有在有实际加入队列的引用时它才工作。 </p>
		<br />
		<a name="listing7">
				<b>清单 7. WeakHashMap.expungeStaleEntries() 的可能实现</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    private void expungeStaleEntries() {
	Entry&lt;K,V&gt; e;
        while ( (e = (Entry&lt;K,V&gt;) queue.poll()) != null) {
            int hash = e.hash;

            Entry&lt;K,V&gt; prev = getChain(hash);
            Entry&lt;K,V&gt; cur = prev;
            while (cur != null) {
                Entry&lt;K,V&gt; next = cur.next;
                if (cur == e) {
                    if (prev == e)
                        setChain(hash, next);
                    else
                        prev.next = next;
                    break;
                }
                prev = cur;
                cur = next;
            }
        }
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>弱引用和弱集合是对堆进行管理的强大工具，使得应用程序可以使用更复杂的可及性方案，而不只是由普通（强）引用所提供的“要么全部要么没有”可及性。下个月，我们将分析与弱引用有关的<i>软引用</i>，将分析在使用弱引用和软引用时，垃圾收集器的行为。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:42 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: Web 层的状态复制</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65593.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:41:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65593.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65593.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65593.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65593.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65593.html</trackback:ping><description><![CDATA[
		<blockquote>大多数具有一定重要性的 Web 应用程序都要求维护某种会话状态，如用户购物车的内容。如何在群集服务器应用程序中管理和复制状态对应用程序的可伸缩性有显著影响。许多 J2SE 和 J2EE 应用程序将状态存储在由 Servlet API 提供的 HttpSession 中。本月，专栏作家 Brian Goetz 分析了状态复制的一些选项以及如何最有效地使用 HttpSession 以提供好的伸缩性和性能。在本文论坛中与本文作者和其他读者分享您的观点。（可以单击文章顶部或者底部的 <b>讨论</b>访问论坛。） </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>不管正在构建的是 J2EE 还是 J2SE 服务器应用程序，都有可能以某种方式使用 Java Servlet —— 可能是直接地通过像 JSP 技术、Velocity 或者 WebMacro 这样的表示层，也可能通过一个基于 servlet 的 Web 服务实现，如 Axis 或者 Glue。Servlet API 提供的一个最重要的功能是会话管理 —— 通过 <code><font face="Courier" size="2">HttpSession</font></code> 接口进行用户状态的认证、失效和维护。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">会话状态</font>
								</strong>
						</span>
				</a>
		</p>
		<p>几乎每一个 Web 应用程序都有一些会话状态，这些状态有可能像记住您是否已登录这么简单，也可能是您的会话的更详细的历史，如购物车的内容、以前查询结果的缓存或者 20 页动态问卷表的完整响应历史。因为 HTTP 协议本身是无状态的，所以需要将会话状态存储在某处并与浏览会话以某种方式相关联，使得下次请求同一 Web 应用程序的页面时可以容易地获取。幸运的是，J2EE 提供了几种管理会话状态的方法 —— 状态可以存储在数据层，用 Servlet API 的 <code><font face="Courier" size="2">HttpSession</font></code> 接口存储在 Web 层，用有状态会话 bean 存储在 Enterprise JavaBeans（EJB）层，甚至用 cookie 或者隐藏表单字段将状态存储在客户层。不幸的是，会话状态管理不当会带来严重的性能问题。 </p>
		<p>如果应用程序能够在 <code><font face="Courier" size="2">HttpSession</font></code> 中存储用户状态，这种方法通常比其他方法更好。在客户端用 HTTP cookie 或者隐藏表单字段存储会话状态有很大的安全风险 —— 它将应用程序的一部分内部内容暴露给了非受信任的客户层。（一个早期的电子商务网站将购物车内容（包括价格）存储在隐藏表单字段中，从而可以很容易被非法利用，让任何了解 HTML 和 HTTP 的用户可以以 0.01 美元购买任何商品。噢）此外，使用 cookie 或者隐藏表单字段很混乱，容易出错，并且脆弱（如果用户禁止在浏览器中使用 cookie，那么基于 cookie 的方法就完全不能工作）。 </p>
		<p>在 J2EE 应用程序中存储服务器端状态的其他方法是使用有状态会话 bean，或者在数据库中存储会话状态。虽然有状态会话 bean 在会话状态管理方面有更大的灵活性，但是在可能的情况下，将会话状态存储在 Web 层仍然有好处。如果业务对象是无状态的，那么通常可以仅仅添加更多 Web 服务器来扩展应用程序，而不用添加更多 Web 服务器和更多 EJB 容器， 这样的成本一般要低一些并且容易完成。使用 <code><font face="Courier" size="2">HttpSession</font></code> 存储会话状态的另一个好处是 Servlet API 提供了一种会话失效时通知的容易方法。在数据库中存储会话状态的成本可能难以承受。 </p>
		<p>servlet 规范没有要求 servlet 容器进行某种类型的会话复制或者持久性，但是它建议将状态复制作为 servlet 首要 <i>存在理由（raison d'etre）</i> 的重要部分，并且它对作为进行会话复制的容器提出了一些要求。会话复制可以提供大量好处 —— 负载平衡、伸缩性、容错和高可用性。相应地，大多数 servlet 容器支持某种形式的 <code><font face="Courier" size="2">HttpSession</font></code> 复制，但是复制的机制、配置和时间是由实现决定的。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07294/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">HttpSession API</font>
								</strong>
						</span>
				</a>
		</p>
		<p>简单地说， <code><font face="Courier" size="2">HttpSession</font></code> 接口支持几种方法，servlet、JSP 页或者其他表示层组件可以用这些方法来跨多个 HTTP 请求维护会话信息。会话绑定到特定的用户，但是在 Web 应用程序的所有 servlet 中共享 —— 不特定于某一个 servlet。一种考虑会话的有用方法是，会话像一个在会话期间存储对象的 <code><font face="Courier" size="2">Map</font></code> —— 可以用 <code><font face="Courier" size="2">setAttribute</font></code> 按名字存储会话属性，并用 <code><font face="Courier" size="2">getAttribute</font></code> 提取它们。 <code><font face="Courier" size="2">HttpSession</font></code> 接口还包含会话生存周期方法，如 <code><font face="Courier" size="2">invalidate()</font></code> （它通知容器应丢弃会话）。清单 1 显示 <code><font face="Courier" size="2">HttpSession</font></code> 接口最常用的元素： </p>
		<br />
		<a name="listing1">
				<b>清单 1. HttpSession API</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public interface HttpSession {
    Object getAttribute(String s);
    Enumeration getAttributeNames();
    void setAttribute(String s, Object o);
    void removeAttribute(String s);

    boolean isNew();
    void invalidate();
    void setMaxInactiveInterval(int i);
    int getMaxInactiveInterval();
    ...
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>理论上，可以跨群集一致性地完全复制会话状态，这样群集中的所有节点都可以服务任何请求，一个简单的负载平衡器可以以轮询方式传送请求，避开有故障的主机。不过，这种紧密的复制有很高的性能成本，并且难于实现，当群集接近某一规模时，还会有伸缩性的问题。</p>
		<p>一种更常用的方式是将负载平衡与会话相似性（affinity） 结合起来 —— 负载平衡器可以将会话与连接相关联，并将会话中以后的请求发送给同一服务器。有很多硬件和软件负载平衡器支持这个功能，并且这意味着只有主连接主机和会话需要故障转移到另一台服务器时才访问复制的会话信息。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07294/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">复制方式</font>
								</strong>
						</span>
				</a>
		</p>
		<p>复制提供了一些可能的好处，包括可用性、容错和伸缩性。此外，有大量会话复制的方法可用：方法的选择取决于应用程序群集的规模、复制的目标和 servlet 容器支持的复制设施。复制有性能成本，包括 CPU 周期（存储在会话中的序列化对象）、网络带宽（广播更新），以及基于磁盘的方案中写入到磁盘或者数据库的成本。</p>
		<p>几乎所有 servlet 容器都通过存储在 <code><font face="Courier" size="2">HttpSession</font></code> 中的序列化对象进行 <code><font face="Courier" size="2">HttpSession</font></code> 复制，所以如果是创建一个分布式应用程序，应当确保只将可序列化对象放到会话中。（一些容器对像 EJB 引用、事务上下文、还有其他非可序列化的 J2EE 对象类型有特殊的处理。） </p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>基于 JDBC 的复制</strong>
						</span>
				</a>
		</p>
		<p>一种会话复制的方法是序列化会话内容并将它写入数据库。这种方法相当直观，其优点是不仅会话可以故障转移到其他主机，而且即使整个群集失效，会话数据也可以保存下来。基于数据库的复制的缺点是性能成本 —— 数据库事务是昂贵的。虽然它可以在 Web 层很好地伸缩，但是它可能在数据层产生伸缩问题 —— 如果群集增长大到一定程度，扩展数据层以容纳会话数据会很困难或者成本无法接受。</p>
		<p>
				<a name="3.2">
						<span class="smalltitle">
								<strong>基于文件的复制</strong>
						</span>
				</a>
		</p>
		<p>基于文件的复制类似于使用数据库存储序列化的会话，只不过是使用共享文件服务器而不是数据库来存储会话数据。这种方式的成本一般比使用数据库的成本（硬件成本、软件许可证和计算开销）低，其代价则是可靠性（数据库可提供比文件系统更强的持久化保证）。</p>
		<p>
				<a name="3.3">
						<span class="smalltitle">
								<strong>基于内存的复制</strong>
						</span>
				</a>
		</p>
		<p>另一种复制方式是与群集中的一个或者多个其他服务器共享序列化的会话数据副本。复制所有会话到所有主机中提供了最大的可用性，并且负载平衡最容易，但是因为复制消息所消耗的每个节点的内存和网络带宽，最终会限制群集的规模。一些应用服务器支持与“伙伴（buddy）”节点的基于内存的复制，其中每一个会话存在于主服务器上和一台（或更多）备份服务器上。这种方案比将所有会话复制到所有服务器的伸缩性更好，但是当需要将会话故障转移到另一台服务器上时会使负载平衡任务复杂化，因为它必须找出另外哪一台（几台）服务器有这个会话。</p>
		<p>
				<a name="3.4">
						<span class="smalltitle">
								<strong>时间考虑</strong>
						</span>
				</a>
		</p>
		<p>除了决定如何存储复制会话数据，还有什么时候复制数据的问题。最可靠但也最昂贵的方法是每次数据改变时复制它（如每次 servlet 调用结束）。不那么昂贵、但是在故障时会有丢失一些数据的风险的方法是在每超过 N 秒时复制数据。</p>
		<p>与时间问题有关的问题是，是复制整个会话还是只试尝复制会话中改变了的属性（它包含的数据会少得多）。这些都需要在可靠性和性能之间进行取舍。Servlet 开发人员应当认识到在故障转移时，会话状态可能变得“过时”（是几次请求前的复制），并应当准备处理不是最新的会话内容。（例如，如果一个interview 的第 3 步产生一个会话属性，而用户在第 4 步时，请求被故障转移到一个具有两次请求之前的会话状态复制的系统上，那么第 4 步的 servlet 代码应预备在会话中找不到这个属性，并采取相应的行动 —— 如重定向，而不是认定它会在那里、并在找不到它时抛出一个 <code><font face="Courier" size="2">NullPointerException</font></code> 。） </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07294/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">容器支持</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Servlet 容器的 <code><font face="Courier" size="2">HttpSession</font></code> 复制选项以及如何配置这些选项是各不相同的。IBM WebSphere ®提供的复制选项是最多的，它提供了在内存中复制或者基于数据库的复制、在 servlet 末尾或者基于时间的复制时间、传播全部会话快照（JBoss 3.2 或以后版本）或者只传播改变了的属性等选择。基于内存的复制基于 JMS 发布-订阅，它可以复制到所有克隆、一个“伙伴”复制品或者一个专门的复制服务器。 </p>
		<p>WebLogic 还提供了一组选择，包括内存中（使用一个伙伴复制品）、基于文件的或者基于数据库的。JBoss 与 Tomcat 或者 Jetty servlet 容器一同使用时，进行基于内存的复制，可以选择 servlet 末尾或者基于时间的复制时间，而快照选项（在 JBoss 3.2 或以后版本）是只复制改变了的属性。Tomcat 5.0 为所有群集节点提供了基于内存的复制。此外，通过像 WADI 这样的项目，可以用 servlet 过滤机制将会话复制添加到像 Tomcat 或者 Jetty 这样的 servlet 容器中。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07294/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5.0">
						<span class="atitle">
								<strong>
										<font size="4">改进分布式 Web 应用程序的性能</font>
								</strong>
						</span>
				</a>
		</p>
		<p>不管决定使用什么机制进行会话复制，可以用几种方式改进 Web 应用程序的性能和伸缩性。首先记住，为了获得会话复制的好处，需要在部署描述符中将 Web 应用程序标记为 distributable，并保证在会话中的所有内容都是可序列化的。</p>
		<p>
				<a name="5.1">
						<span class="smalltitle">
								<strong>保持会话最小</strong>
						</span>
				</a>
		</p>
		<p>因为复制会话有随着会话中的对象图（object graph） 的变大而增加成本，所以应当尽可能地在会话中少放置数据。这样做会减少复制的序列化的开销、网络带宽要求和磁盘要求。特别地，将共享对象存储在会话中一般不是好主意，因为它们需要复制到它们所属的 <i>每一个</i>会话中。 </p>
		<p>
				<a name="5.2">
						<span class="smalltitle">
								<strong>不要绕过 setAttribute</strong>
						</span>
				</a>
		</p>
		<p>在改变会话的属性时，要知道即使 servlet 容器只是试图做最小的更新（只传播改变了的属性），如果没有调用 <code><font face="Courier" size="2">setAttribute</font></code> ，容器也可能没有注意到已经改变的属性。（想像在会话中有一个 <code><font face="Courier" size="2">Vector</font></code> ，表示购物车中的商品 —— 如果调用 <code><font face="Courier" size="2">getAttribute()</font></code> 获取 <code><font face="Courier" size="2">Vector</font></code> 、然后向它添加一些内容，并且不再次调用 <code><font face="Courier" size="2">setAttribute</font></code> ，容器可能不会意识到 <code><font face="Courier" size="2">Vector</font></code> 已经改变了。） </p>
		<p>
				<a name="5.3">
						<span class="smalltitle">
								<strong>使用细化的会话属性</strong>
						</span>
				</a>
		</p>
		<p>对于支持最小更新的容器，可以通过将多个细化的对象而不是一个大块头放到会话中而降低会话复制的成本。这样，对快速改变的数据的改变也不会迫使容器去序列化并传播慢速改变的数据。</p>
		<p>
				<a name="5.4">
						<span class="smalltitle">
								<strong>完成后使之失效</strong>
						</span>
				</a>
		</p>
		<p>如果知道用户完成了会话的使用（如，用户选择注销登录），确保调用 <code><font face="Courier" size="2">HttpSession.invalidate()</font></code> 。否则，会话将持久化直到它失效，这会消耗内存，并且可能是长时间的（取决于会话超时时间）。许多 servlet 容器对可以跨所有会话使用的内存的数量有一个限制，达到这个限制时，会序列化最先使用的会话并将它写到磁盘上。如果知道用户使用完了会话，可以使容器不再处理它并使它作废。 </p>
		<p>
				<a name="5.5">
						<span class="smalltitle">
								<strong>保持会话干净</strong>
						</span>
				</a>
		</p>
		<p>如果在会话中有大的项，并且只在会话的一部分中使用，那么当不再需要时应删除它们。删除它们会减少会话复制的成本。（这种做法类似于使用显式 nulling 以帮助垃圾收集器，老读者知道我一般不建议这样做，但是在这种情况下，因为有复制，在会话中保持垃圾的成本要高得多，因此值得以这种方式帮助容器。）</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp07294/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="6.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>通过 <code><font face="Courier" size="2">HttpSession</font></code> 复制，Servlet 容器可以在构建复制的、高可用性的 Web 应用程序方面给您减轻很多负担。不过，对于复制有一些配置选项，每个容器都不一样，复制策略的选择对于应用程序的容错、性能和伸缩性有影响。复制策略的选择不应当是事后的 —— 您应当在构建 Web 应用程序时就考虑它。并且，一定不要忘记进行负载测试以确定应用程序的伸缩性 —— 在客户替您做之前。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65593.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:41 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65593.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 关于异常的争论</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65592.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:39:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65592.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65592.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65592.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65592.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65592.html</trackback:ping><description><![CDATA[
		<blockquote>关于在 Java 语言中使用异常的大多数建议都认为，在确信异常可以被捕获的任何情况下，应该优先使用检查型异常。语言设计（编译器强制您在方法签名中列出可能被抛出的所有检查型异常）以及早期关于样式和用法的著作都支持该建议。最近，几位著名的作者已经开始认为非检查型异常在优秀的 Java 类设计中有着比以前所认为的更为重要的地位。在本文中，Brian Goetz 考察了关于使用非检查型异常的优缺点。请在附带的讨论论坛中与作者和其他读者一起分享您有关本文的心得体会（您也可以点击文章顶部或底部的 <b>讨论</b>来访问该论坛。） </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>与 C++ 类似，Java 语言也提供异常的抛出和捕获。但是，与 C++ 不一样的是，Java 语言支持检查型和非检查型异常。Java 类必须在方法签名中声明它们所抛出的任何检查型异常，并且对于任何方法，如果它调用的方法抛出一个类型为 E 的检查型异常，那么它必须捕获 E 或者也声明为抛出 E（或者 E 的一个父类）。通过这种方式，该语言强制我们文档化控制可能退出一个方法的所有预期方式。</p>
		<p>对于因为编程错误而导致的异常，或者是不能期望程序捕获的异常（解除引用一个空指针，数组越界，除零，等等），为了使开发人员免于处理这些异常，一些异常被命名为非检查型异常（即那些继承自 <code><font face="Courier" size="2">RuntimeException</font></code> 的异常）并且不需要进行声明。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">传统的观点</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在下面的来自 Sun 的“The Java Tutorial”的摘录中，总结了关于将一个异常声明为检查型还是非检查型的传统观点（更多的信息请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#resources"><font color="#996699">参考资料</font></a>）： </p>
		<blockquote>因为 Java 语言并不要求方法捕获或者指定运行时异常，因此编写只抛出运行时异常的代码或者使得他们的所有异常子类都继承自 <code><font face="Courier" size="2">RuntimeException</font></code> ，对于程序员来说是有吸引力的。这些编程捷径都允许程序员编写 Java 代码而不会受到来自编译器的所有挑剔性错误的干扰，并且不用去指定或者捕获任何异常。尽管对于程序员来说这似乎比较方便，但是它回避了 Java 的捕获或者指定要求的意图，并且对于那些使用您提供的类的程序员可能会导致问题。 <br /><br />检查型异常代表关于一个合法指定的请求的操作的有用信息，调用者可能已经对该操作没有控制，并且调用者需要得到有关的通知 —— 例如，文件系统已满，或者远端已经关闭连接，或者访问权限不允许该动作。 <br /><br />如果您仅仅是因为不想指定异常而抛出一个 <code><font face="Courier" size="2">RuntimeException</font></code> ，或者创建 <code><font face="Courier" size="2">RuntimeException</font></code> 的一个子类，那么您换取到了什么呢？您只是获得了抛出一个异常而不用您指定这样做的能力。换句话说，这是一种用于避免文档化方法所能抛出的异常的方式。在什么时候这是有益的？也就是说，在什么时候避免注明一个方法的行为是有益的？答案是“几乎从不。” </blockquote>
		<p>换句话说，Sun 告诉我们检查型异常应该是准则。该教程通过多种方式继续说明，通常应该抛出异常，而不是 <code><font face="Courier" size="2">RuntimeException</font></code> —— 除非您是 JVM。 </p>
		<p>在 <i>Effective Java: Programming Language Guide</i>一书中（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#resources"><font color="#996699">参考资料</font></a>），Josh Bloch 提供了下列关于检查型和非检查型异常的知识点，这些与 “The Java Tutorial” 中的建议相一致（但是并不完全严格一致）： </p>
		<ul>
				<li>
						<b>第 39 条：只为异常条件使用异常。</b>也就是说，不要为控制流使用异常，比如，在调用 <code><font face="Courier" size="2">Iterator.next()</font></code> 时而不是在第一次检查 <code><font face="Courier" size="2">Iterator.hasNext()</font></code> 时捕获 <code><font face="Courier" size="2">NoSuchElementException</font></code> 。 <br /><br /></li>
				<li>
						<b>第 40 条：为可恢复的条件使用检查型异常，为编程错误使用运行时异常。</b>这里，Bloch 回应传统的 Sun 观点 —— 运行时异常应该只是用于指示编程错误，例如违反前置条件。 <br /><br /></li>
				<li>
						<b>第 41 条：避免不必要的使用检查型异常。</b>换句话说，对于调用者不可能从其中恢复的情形，或者惟一可以预见的响应将是程序退出，则不要使用检查型异常。 <br /><br /></li>
				<li>
						<b>第 43 条：抛出与抽象相适应的异常。</b>换句话说，一个方法所抛出的异常应该在一个抽象层次上定义，该抽象层次与该方法做什么相一致，而不一定与方法的底层实现细节相一致。例如，一个从文件、数据库或者 JNDI 装载资源的方法在不能找到资源时，应该抛出某种 <code><font face="Courier" size="2">ResourceNotFound</font></code> 异常（通常使用异常链来保存隐含的原因），而不是更底层的 <code><font face="Courier" size="2">IOException</font></code> 、 <code><font face="Courier" size="2">SQLException</font></code> 或者 <code><font face="Courier" size="2">NamingException</font></code> 。 </li>
		</ul>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">重新考察非检查型异常的正统观点</font>
								</strong>
						</span>
				</a>
		</p>
		<p>最近，几位受尊敬的专家，包括 Bruce Eckel 和 Rod Johnson，已经公开声明尽管他们最初完全同意检查型异常的正统观点，但是他们已经认定排他性使用检查型异常的想法并没有最初看起来那样好，并且对于许多大型项目，检查型异常已经成为一个重要的问题来源。Eckel 提出了一个更为极端的观点，建议所有的异常应该是非检查型的；Johnson 的观点要保守一些，但是仍然暗示传统的优先选择检查型异常是过分的。（值得一提的是，C# 的设计师在语言设计中选择忽略检查型异常，使得所有异常都是非检查型的，因而几乎可以肯定他们具有丰富的 Java 技术使用经验。但是，后来他们的确为检查型异常的实现留出了空间。） </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">对于检查型异常的一些批评</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Eckel 和 Johnson 都指出了一个关于检查型异常的相似的问题清单；一些是检查型异常的内在属性，一些是检查型异常在 Java 语言中的特定实现的属性，还有一些只是简单的观察，主要是关于检查型异常的广泛的错误使用是如何变为一个严重的问题，从而导致该机制可能需要被重新考虑。</p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>检查型异常不适当地暴露实现细节</strong>
						</span>
				</a>
		</p>
		<p>您已经有多少次看见（或者编写）一个抛出 <code><font face="Courier" size="2">SQLException</font></code> 或者 <code><font face="Courier" size="2">IOException</font></code> 的方法，即使它看起来与数据库或者文件毫无关系呢？对于开发人员来说，在一个方法的最初实现中总结出可能抛出的所有异常并且将它们增加到方法的 throws 子句（许多 IDE 甚至帮助您执行该任务）是十分常见的。这种直接方法的一个问题是它违反了 Bloch 的 第 43 条 —— 被抛出的异常所位于的抽象层次与抛出它们的方法不一致。 </p>
		<p>一个用于装载用户概要的方法，在找不到用户时应该抛出 <code><font face="Courier" size="2">NoSuchUserException</font></code> ，而不是 <code><font face="Courier" size="2">SQLException</font></code> —— 调用者可以很好地预料到用户可能找不到，但是不知道如何处理 <code><font face="Courier" size="2">SQLException</font></code> 。异常链可以用于抛出一个更为合适的异常而不用丢弃关于底层失败的细节（例如栈跟踪），允许抽象层将位于它们之上的分层同位于它们之下的分层的细节隔离开来，同时保留对于调试可能有用的信息。 </p>
		<p>据说，诸如 JDBC 包的设计采取这样一种方式，使得它难以避免该问题。在 JDBC 接口中的每个方法都抛出 <code><font face="Courier" size="2">SQLException</font></code> ，但是在访问一个数据库的过程中可能会经历多种不同类型的问题，并且不同的方法可能易受不同错误模式的影响。一个 <code><font face="Courier" size="2">SQLException</font></code> 可能指示一个系统级问题（不能连接到数据库）、逻辑问题（在结果集中没有更多的行）或者特定数据的问题（您刚才试图插入行的主键已经存在或者违反实体完整性约束）。如果没有犯不可原谅的尝试分析消息正文的过失，调用者是不可能区分这些不同类型的 <code><font face="Courier" size="2">SQLException</font></code> 的。（ <code><font face="Courier" size="2">SQLException</font></code> 的确支持用于获取数据库特定错误代码和 SQL 状态变量的方法，但是在实践中这些很少用于区分不同的数据库错误条件。） </p>
		<p>
				<a name="3.2">
						<span class="smalltitle">
								<strong>不稳定的方法签名</strong>
						</span>
				</a>
		</p>
		<p>不稳定的方法签名问题是与前面的问题相关的 —— 如果您只是通过一个方法传递异常，那么您不得不在每次改变方法的实现时改变它的方法签名，以及改变调用该方法的所有代码。一旦类已经被部署到产品中，管理这些脆弱的方法签名就变成一个昂贵的任务。然而，该问题本质上是没有遵循 Bloch 提出的第 43 条的另一个症状。方法在遇到失败时应该抛出一个异常，但是该异常应该反映该方法做什么，而不是它如何做。</p>
		<p>有时，当程序员对因为实现的改变而导致从方法签名中增加或者删除异常感到厌烦时，他们不是通过使用一个抽象来定义特定层次可能抛出的异常类型，而只是将他们的所有方法都声明为抛出 <code><font face="Courier" size="2">Exception</font></code> 。换句话说，他们已经认定异常只是导致烦恼，并且基本上将它们关闭掉了。毋庸多言，该方法对于绝大多数可任意使用的代码来说通常不是一个好的错误处理策略。 </p>
		<p>
				<a name="3.3">
						<span class="smalltitle">
								<strong>难以理解的代码</strong>
						</span>
				</a>
		</p>
		<p>因为许多方法都抛出一定数目的不同异常，错误处理的代码相对于实际的功能代码的比率可能会偏高，使得难以找到一个方法中实际完成功能的代码。异常是通过集中错误处理来设想减小代码的，但是一个具有三行代码和六个 catch 块（其中每个块只是记录异常或者包装并重新抛出异常）的方法看起来比较膨胀并且会使得本来简单的代码变得模糊。</p>
		<p>
				<a name="3.4">
						<span class="smalltitle">
								<strong>异常淹没</strong>
						</span>
				</a>
		</p>
		<p>我们都看到过这样的代码，其中捕获了一个异常，但是在 <code><font face="Courier" size="2">catch</font></code> 块中没有代码。尽管这种编程实践很明显是不好的，但是很容易看出它是如何发生的 —— 在原型化期间，某人通过 <code><font face="Courier" size="2">try...catch</font></code> 块包装代码，而后来忘记返回并填充 <code><font face="Courier" size="2">catch</font></code> 块。尽管这个错误很常见，但是这也是更好的工具可以帮助我们的地方之一 —— 对于异常淹没的地方，通过编辑器、编译器或者静态检查工具可以容易地检测并发出警告。 </p>
		<p>极度通用的 <code><font face="Courier" size="2">try...catch</font></code> 块是另一种形式的异常淹没，并且更加难以检测，因为这是 Java 类库中的异常类层次的结构而导致的（可疑）。让我们假定一个方法抛出四个不同类型的异常，并且调用者遇到其中任何一个异常都将捕获、记录它们，并且返回。实现该策略的一种方式是使用一个带有四个 <code><font face="Courier" size="2">catch</font></code> 子句的 <code><font face="Courier" size="2">try...catch</font></code> 块，其中每个异常类型一个。为了避免代码难以理解的问题，一些开发人员将重构该代码，如清单 1 所示： </p>
		<br />
		<a name="N10158">
				<b>清单 1. 意外地淹没 RuntimeException</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">try { 
  doSomething();
}
catch (Exception e) { 
  log(e);
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>尽管该代码与四个 <code><font face="Courier" size="2">catch</font></code> 块相比更为紧凑，但是它具有一个问题 —— 它还捕获可能由 <code><font face="Courier" size="2">doSomething</font></code> 抛出的任何 <code><font face="Courier" size="2">RuntimeException</font></code> 并且阻止它们进行扩散。 </p>
		<p>
				<a name="3.5">
						<span class="smalltitle">
								<strong>过多的异常包装</strong>
						</span>
				</a>
		</p>
		<p>如果异常是在一个底层的设施中生成的，并且通过许多代码层向上扩散，在最终被处理之前它可能被捕获、包装和重新抛出若干次。当异常最终被记录的时候，栈跟踪可能有许多页，因为栈跟踪可能被复制多次，其中每个包装层一次。（在 JDK 1.4 以及后来的版本中，异常链的实现在某种程度上缓解了该问题。）</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">替换的方法</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Bruce Eckel， <i>Thinking in Java</i>（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#resources"><font color="#996699">参考资料</font></a>）的作者，声称在使用 Java 语言多年后，他已经得出这样的结论，认为检查型异常是一个错误 —— 一个应该被声明为失败的试验。Eckel 提倡将所有的异常都作为非检查型的，并且提供清单 2 中的类作为将检查型异常转变为非检查型异常的一个方法，同时保留当异常从栈向上扩散时捕获特定类型的异常的能力（关于如何使用该方法的解释，请参阅他在 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#resources"><font color="#996699">参考资料</font></a>小节中的文章）： </p>
		<br />
		<a name="code2">
				<b>清单 2. Eckel 的异常适配器类</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class ExceptionAdapter extends RuntimeException {
  private final String stackTrace;
  public Exception originalException;
  public ExceptionAdapter(Exception e) {
    super(e.toString());
    originalException = e;
    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));
    stackTrace = sw.toString();
  }
  public void printStackTrace() { 
    printStackTrace(System.err);
  }
  public void printStackTrace(java.io.PrintStream s) { 
    synchronized(s) {
      s.print(getClass().getName() + ": ");
      s.print(stackTrace);
    }
  }
  public void printStackTrace(java.io.PrintWriter s) { 
    synchronized(s) {
      s.print(getClass().getName() + ": ");
      s.print(stackTrace);
    }
  }
  public void rethrow() { throw originalException; }
} 
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果查看 Eckel 的 Web 站点上的讨论，您将会发现回应者是严重分裂的。一些人认为他的提议是荒谬的；一些人认为这是一个重要的思想。（我的观点是，尽管恰当地使用异常确实是很难的，并且对异常用不好的例子大量存在，但是大多数赞同他的人是因为错误的原因才这样做的，这与一个政客位于一个可以随便获取巧克力的平台上参选将会获得十岁孩子的大量选票的情况具有相似之处。）</p>
		<p>Rod Johnson 是 <i>J2EE Design and Development</i>（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#resources"><font color="#996699">参考资料</font></a>） 的作者，这是我所读过的关于 Java 开发，J2EE 等方面的最好的书籍之一。他采取一个不太激进的方法。他列举了异常的多个类别，并且为每个类别确定一个策略。一些异常本质上是次要的返回代码（它通常指示违反业务规则），而一些异常则是“发生某种可怕错误”（例如数据库连接失败）的变种。Johnson 提倡对于第一种类别的异常（可选的返回代码）使用检查型异常，而对于后者使用运行时异常。在“发生某种可怕错误”的类别中，其动机是简单地认识到没有调用者能够有效地处理该异常，因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小（并且最小化异常淹没的可能性）。 </p>
		<p>Johnson 还列举了一个中间情形，对此他提出一个问题，“只是少数调用者希望处理问题吗？”对于这些情形，他也建议使用非检查型异常。作为该类别的一个例子，他列举了 JDO 异常 —— 大多数情况下，JDO 异常表示的情况是调用者不希望处理的，但是在某些情况下，捕获和处理特定类型的异常是有用的。他建议在这里使用非检查型异常，而不是让其余的使用 JDO 的类通过捕获和重新抛出这些异常的形式来弥补这个可能性。</p>
		<p>
				<a name="3.6">
						<span class="smalltitle">
								<strong>使用非检查型异常</strong>
						</span>
				</a>
		</p>
		<p>关于是否使用非检查型异常的决定是复杂的，并且很显然没有明显的答案。Sun 的建议是对于任何情况使用它们，而 C# 方法（也就是 Eckel 和其他人所赞同的）是对于任何情况都不使用它们。其他人说，“还存在一个中间情形。”</p>
		<p>通过在 C++ 中使用异常，其中所有的异常都是非检查型的，我已经发现非检查型异常的最大风险之一就是它并没有按照检查型异常采用的方式那样自我文档化。除非 API 的创建者明确地文档化将要抛出的异常，否则调用者没有办法知道在他们的代码中将要捕获的异常是什么。不幸的是，我的经验是大多数 C++ API 的文档化非常差，并且即使文档化很好的 API 也缺乏关于从一个给定方法可能抛出的异常的足够信息。我看不出有任何理由可以说该问题对于 Java 类库不是同样的常见，因为 Jav 类库严重依赖于非检查型异常。依赖于您自己的或者您的合作伙伴的编程技巧是非常困难的；如果不得不依赖于某个人的文档化技巧，那么对于他的代码您可能得使用调用栈中的十六个帧来作为您的主要的错误处理机制，这将会是令人恐慌的。</p>
		<p>文档化问题进一步强调为什么懒惰是导致选择使用非检查型异常的一个不好的原因，因为对于文档化增加给包的负担，使用非检查型异常应该比使用检查型异常甚至更高（当文档化您所抛出的非检查型异常比检查型异常变得更为重要的时候）。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05254/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">文档化，文档化，文档化</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果决定使用非检查型异常，您需要彻底地文档化这个选择，包括在 Javadoc 中文档化一个方法可能抛出的所有非检查型异常。Johnson 建议在每个包的基础上选择检查型和非检查型异常。使用非检查型异常时还要记住，即使您并不捕获任何异常，也可能需要使用 <code><font face="Courier" size="2">try...finally</font></code> 块，从而可以执行清除动作例如关闭数据库连接。对于检查型异常，我们有 <code><font face="Courier" size="2">try...catch</font></code> 用来提示增加一个 <code><font face="Courier" size="2">finally</font></code> 子句。对于非检查型异常，我们则没有这个支撑可以依靠。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65592.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:39 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65592.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论和实践: 理解 JTS ― 平衡安全性和性能</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65591.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:38:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65591.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65591.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65591.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65591.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65591.html</trackback:ping><description><![CDATA[
		<p>为 EJB 组件定义事务划分和隔离属性（attribute）的职责由应用程序装配人员来承担。如果这些属性设置不当，会对应用程序的性能、可伸缩性或容错能力造成严重的后果。不幸的是，并没有一种必须遵守的规则用于正确设置这些属性，但有一些指导可以帮助我们在并发危险和性能危险之间找到一种平衡。</p>
		<p>我们在第 1 部分中讨论过，事务主要是一种异常处理机制。事务在程序中的用途与合法合同在日常业务中的用途相似：如果出了什么问题它们可以帮助恢复。但由于大多数时间内都没实际 <i>发生</i>什么错误，我们就希望能够尽量减少它们的开销以及对其余时间的占用。我们在应用程序中如何使用事务会对应用程序的性能和可伸缩性产生很大的影响。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">事务划分</font>
								</strong>
						</span>
				</a>
		</p>
		<p>J2EE 容器提供了两种机制用来定义事务的起点和终点：bean 管理的事务和容器管理的事务。在 bean 管理的事务中，用 <code><font face="Courier" size="2">UserTransaction.begin()</font></code> 和 <code><font face="Courier" size="2">UserTransaction.commit()</font></code> 在 bean 方法中显式开始和结束一个事务。另一方面，容器管理的事务提供了更多的灵活性。通过在装配描述符中为每个 EJB 方法定义事务性属性，您可以指定每个方法的事务性需求并让容器确定何时开始和结束一个事务。无论在哪种情况下，构建事务的基本指导方针都是一样的。 </p>
		<p>
				<a name="N10076">
						<span class="smalltitle">
								<strong>进来，出去</strong>
						</span>
				</a>
		</p>
		<p>事务划分的第一条规则是“尽量短小”。事务提供并发控制；这通常意味着资源管理器将代表您获得您在事务期间访问的数据项的锁，并且它必须一直持有这些锁，直到事务结束。（请回忆一下本系列第 1 部分所讨论的 <i>ACID</i>特性，其中“ACID”的“I”代表“隔离”（Isolation）。也就是说，一个事务的结果影响不到与该事务并发执行的其它事务。）当您拥有锁时，任何需要访问您锁定的数据项的其它事务将不得不一直等待，直到您释放锁。如果您的事务很长，那些其它的所有事务都将被锁定，您的应用程序吞吐量将大幅度下降。 </p>
		<p>
				<b>规则 1：使事务尽可能短小。</b>
		</p>
		<p>通过使事务尽量短小，您可以把阻碍其它事务的时间缩到最短，从而提高应用程序的可伸缩性。保持事务尽可能短小的最好方法当然是不在事务中间做任何不必要耗费时间的事，特别是不要在事务中间等待用户输入。</p>
		<p>开始一个事务，从数据库检索一些数据，显示数据，然后在仍处于事务中时请用户做出一个选择可能比较诱人。千万别这么做！即使用户注意力集中，也要花费数秒来响应 ― 而在数据库中拥有锁数秒的时间已经是很长的了。如果用户决定离开计算机，或许是去吃午餐或者甚至回家一天，会发生什么情况？应用程序将只好无奈停机。在事务期间执行 I/O 是导致灾难的秘诀。</p>
		<p>
				<b>规则 2：在事务期间不要等待用户输入。</b>
		</p>
		<p>
				<a name="N10094">
						<span class="smalltitle">
								<strong>将相关的操作归在一起</strong>
						</span>
				</a>
		</p>
		<p>由于每个事务都有不小的开销，您可能认为最好是在单个事务中执行尽可能多的操作以使每个操作的开销达到最小。但规则 1 告诉我们长事务对可伸缩性不利。那么如何实现最小化每个操作的开销和可伸缩性之间的平衡呢？</p>
		<p>我们把规则 1 设置为逻辑上的极端 ― 每个事务一个操作 ― 这样不仅会导致额外开销，还会危及应用程序状态的一致性。假定事务性资源管理器维护应用程序状态的一致性（请回忆一下第 1 部分，其中“ACID”的“C”代表“一致性”（Consistency）），但它们依赖应用程序来定义一致性的意思。实际上，我们在描述事务时使用的一致性的定义有点圆滑：应用程序说一致性是什么意思它就是什么意思。应用程序把几组应用程序状态的变化组织到几个事务中，结果应用程序的状态就成了 <i>定义上的（by definition）</i>一致。然后资源管理器确保如果它必须从故障恢复的话，就把应用程序状态恢复到最近的一致状态。 </p>
		<p>在第 1 部分中，我们给出了一个在银行应用程序中将资金从一个帐户转移到另一个帐户的示例。清单 1 展示了这个示例可能的 SQL 实现，它包含 5 个 SQL 操作（一个选择，两个更新和两个插入操作）：</p>
		<br />
		<a name="code1">
				<b>清单 1. 资金转移的样本 SQL 代码</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">SELECT accountBalance INTO aBalance 
    FROM Accounts WHERE accountId=aId;
IF (aBalance &gt;= transferAmount) THEN 
    UPDATE Accounts 
        SET accountBalance = accountBalance - transferAmount
        WHERE accountId = aId;
    UPDATE Accounts 
        SET accountBalance = accountBalance + transferAmount
        WHERE accountId = bId;
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (aId, -transferAmount);
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (bId, transferAmount);
ELSE
    FAIL "Insufficient funds in account";
END IF
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果我们把这个操作作为五个单独的事务来执行会发生什么情况？这样不仅会使执行速度变慢（由于事务开销），还会失去一致性。例如，如果一个人从帐户 A 取了钱，作为执行第一次 SELECT（检查余额）和随后的记入借方 UPDATE 之间的一个单独事务的一部分，会发生什么情况？这样会违反我们认为这段代码会强制遵守的业务规则 ― 帐户余额应该是非负的。如果在第一次 UPDATE 和第二次 UPDATE 之间系统失败会发生什么情况？现在，当系统恢复时，钱已经离开了帐户 A 但还没有记入帐户 B 的贷方，并且也无记录说明原因。这样，哪个帐户的所有者都不会开心。</p>
		<p>清单 1 中的五个 SQL 操作是单个相关操作 ― 将资金从一个帐户转移到另一个帐户 ― 的一部分。因此，我们希望要么全部执行它们，要么一个也不执行，建议在单个事务中全部执行它们。</p>
		<p>
				<b>规则 3：将相关操作归到单个事务中。</b>
		</p>
		<p>
				<a name="N100BC">
						<span class="smalltitle">
								<strong>理想化的平衡</strong>
						</span>
				</a>
		</p>
		<p>规则 1 说事务应尽可能短小。清单 1 中的示例表明有时候我们必须把一些操作归到一个事务中来维护一致性。当然，它要依赖应用程序来确定“相关操作”是由什么组成的。我们可以把规则 1 和 3 结合在一起，提供一个描述事务范围的一般指导，我们规定它为规则 4：</p>
		<p>
				<b>规则 4：把相关操作归到单个事务中，但把不相关的操作放到单独的事务中。</b>
		</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0514/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">容器管理的事务</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在使用容器管理的事务时，不是显式声明事务的起点和终点，而是为每个 EJB 方法定义事务性需求。bean 的 <code><font face="Courier" size="2">assembly-descriptor</font></code> 的 <code><font face="Courier" size="2">container-transaction</font></code> 部分的 <code><font face="Courier" size="2">trans-attribute</font></code> 元素中定义了事务模式。（清单 2 中显示了一个 <code><font face="Courier" size="2">assembly-descriptor</font></code> 示例。）方法的事务模式以及状态 ― 调用方法是否早已在事务中被征用 ― 决定了当 EJB 方法被调用时容器应该进行下面几个操作中的哪一个： </p>
		<ul>
				<li>征用现有事务中的方法。 
</li>
				<li>创建一个新事务，并征用该事务中的方法。 
</li>
				<li>不征用任何事务中的方法。 
</li>
				<li>抛出一个异常。 </li>
		</ul>
		<br />
		<a name="code2">
				<b>清单 2. 样本 EJB 装配描述符</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">&lt;assembly-descriptor&gt;
  ...
  &lt;container-transaction&gt;
    &lt;method&gt;
      &lt;ejb-name&gt;MyBean&lt;/ejb-name&gt;
      &lt;method-name&gt;*&lt;/method-name&gt;
    &lt;/method&gt;
    &lt;trans-attribute&gt;Required&lt;/trans-attribute&gt;
  &lt;/container-transaction&gt;
  &lt;container-transaction&gt;
    &lt;method&gt;
      &lt;ejb-name&gt;MyBean&lt;/ejb-name&gt;
      &lt;method-name&gt;logError&lt;/method-name&gt;
    &lt;/method&gt;
    &lt;trans-attribute&gt;RequiresNew&lt;/trans-attribute&gt;
  &lt;/container-transaction&gt;
  ...
&lt;/assembly-descriptor&gt;
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>J2EE 规范定义了六种事务模式： <code><font face="Courier" size="2">Required</font></code> 、 <code><font face="Courier" size="2">RequiresNew</font></code> 、 <code><font face="Courier" size="2">Mandatory</font></code> 、 <code><font face="Courier" size="2">Supports</font></code> 、 <code><font face="Courier" size="2">NotSupported</font></code> 和 <code><font face="Courier" size="2">Never</font></code> 。表 1 概述了每种模式的行为 ― 在现有事务中被调用和不在事务内调用时的行为 ― 并描述了每种模式受哪些类型的 EJB 组件支持。（一些容器可能允许您在选择事务模式时有更多的灵活性，但这种使用要依赖特定于容器的功能，因此不适合跨容器的情况）。 </p>
		<p>
				<a name="table1">
						<span class="smalltitle">
								<strong>表 1. 事务模式</strong>
						</span>
				</a>
		</p>
		<p>
		</p>
		<table cellspacing="0" cellpadding="3" width="600" border="1">
				<tbody>
						<tr valign="top">
								<td>
										<strong>事务模式</strong>
								</td>
								<td>
										<b>Bean 类型</b>
								</td>
								<td>
										<b>在事务 T 内被调用时的行为</b>
								</td>
								<td>
										<b>在事务外被调用时的行为</b>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">Required</font>
										</code>
								</td>
								<td valign="top">会话、实体、消息驱动</td>
								<td valign="top">在 T 中征用</td>
								<td valign="top">新建事务</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">RequiresNew</font>
										</code>
								</td>
								<td valign="top">会话、实体</td>
								<td valign="top">新建事务</td>
								<td valign="top">新建事务</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">Supports</font>
										</code>
								</td>
								<td valign="top">会话、消息驱动</td>
								<td valign="top">在 T 中征用</td>
								<td valign="top">不带事务运行</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">Mandatory</font>
										</code>
								</td>
								<td valign="top">会话、实体</td>
								<td valign="top">在 T 中征用</td>
								<td valign="top">出错</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">NotSupported</font>
										</code>
								</td>
								<td valign="top">会话、消息驱动</td>
								<td valign="top">不带事务运行</td>
								<td valign="top">不带事务运行</td>
						</tr>
						<tr>
								<td valign="top">
										<code>
												<font face="Courier" size="2">Never</font>
										</code>
								</td>
								<td valign="top">会话、消息驱动</td>
								<td valign="top">出错</td>
								<td valign="top">不带事务运行</td>
						</tr>
				</tbody>
		</table>
		<p>在只使用容器管理的事务的应用程序中，只有组件调用事务模式为 <code><font face="Courier" size="2">Required</font></code> 或 <code><font face="Courier" size="2">RequiresNew</font></code> 的 EJB 方法时才启动事务。如果容器创建一个事务作为调用事务性方法的结果，当该方法完成时将关闭该事务。如果方法正常返回，容器将提交事务（除非应用程序已经要求回滚事务）。如果方法通过抛出一个异常退出，容器将回滚事务并传播该异常。如果在现有事务 T 中调用了一个方法，并且事务模式指定应该不带事务运行该方法或者在新事务中运行该方法，那么事务 T 将被暂挂，一直到方法完成，然后先前的事务 T 被恢复。 </p>
		<p>
				<a name="N101D9">
						<span class="smalltitle">
								<strong>选择一种事务模式</strong>
						</span>
				</a>
		</p>
		<p>那么我们应该为自己的 bean 方法选择哪种模式呢？对于会话 bean 和消息驱动 bean，您通常想使用 <code><font face="Courier" size="2">Required</font></code> 来确保每个调用都被作为事务的一部分执行，但仍将允许方法作为一个更大的事务的组件。请小心使用 <code><font face="Courier" size="2">RequiresNew</font></code> ；只有在确定自己的方法的行为应该与调用您的方法的行为分开提交时，才应该使用这种模式。 <code><font face="Courier" size="2">RequiresNew</font></code> 一般情况下只和与系统中其它对象关系很少或没什么关系的对象（比如日志对象）一起使用。（把 <code><font face="Courier" size="2">RequiresNew</font></code> 与日志对象一起使用比较有意义，因为您可能希望在不管外围事务是否提交的情况下提交日志消息。） </p>
		<p>
				<code>
						<font face="Courier" size="2">RequiresNew</font>
				</code> 使用不当会导致与上面的描述相似的情况，其中，清单 1 中的代码在五个分开的事务而不是一个事务中执行，这样会使应用程序处于不一致状态。 </p>
		<p>对于 CMP（容器管理的持久性，container-managed persistence）实体 bean，通常是希望使用 <code><font face="Courier" size="2">Required</font></code> 。 <code><font face="Courier" size="2">Mandatory</font></code> 也是一个合理的选项，特别是在最初开发时；这将会警告您实体 bean 方法在事务外被调用这种情况，这时可能会指出一个部署错误。您几乎从不希望把 <code><font face="Courier" size="2">RequiresNew</font></code> 和 CMP 实体 bean 一起使用。 <code><font face="Courier" size="2">NotSupported</font></code> 和 <code><font face="Courier" size="2">Never</font></code> 旨在用于非事务性资源，比如 Java 事务 API（Java Transaction API，JTA）事务中无法征用的外部非事务性系统或事务性系统的适配器。 </p>
		<p>如果 EJB 应用程序设计得当，应用上面的事务模式指导往往会自然地产生规则 4 建议的事务划分。原因是 J2EE 体系架构鼓励把应用程序分解为最小的方便处理的块，并且每个块都作为一个单独的请求被处理（ 不管是以 HTTP 请求的形式还是作为在 JMS 队列中排队的消息的结果）。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0514/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">重温隔离</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在第 1 部分中，我们定义了 <i>隔离（isolation）</i>的意思是：一个事务的影响对与该事务并发执行的其它事务是不可见的；从事务的角度来看，好象事务是连续执行而非并行执行。尽管事务性资源管理器经常可以同时处理许多事务并提供隔离的假象，但有时隔离限制实际上要求把新事务延迟到现有事务完成后才开始。由于完成一个事务至少包括一个同步磁盘 I/O（写到事务日志），这就会把每秒的事务数限制到接近每秒的写磁盘次数，这对可伸缩性不利。 </p>
		<p>实际上，通常是充分放松隔离需求以允许更多的事务并发执行并使系统响应能够得到改善，使可伸缩性变得更强。几乎所有的数据库都支持标准隔离级别：读未提交的（Read Uncommitted）、读已提交的（Read Committed）、可重复的读（Repeatable Read） 和可串行化的（Serializable）。</p>
		<p>不幸的是，为容器管理的事务管理隔离目前是在 J2EE 规范的范围之外。但是，许多 J2EE 容器，比如 IBM WebSphere 和 BEA WebLogic，将提供特定于容器的扩展，这些扩展允许您以每方法（per-method）为基础设置事务隔离级别，设置方法与在装配描述符中设置事务模式的方法相同。对于 bean 管理的事务，您可以通过 JDBC 或者其它资源管理器连接设置隔离级别。</p>
		<p>为阐明隔离级别之间的差异，我们首先把几个并发危险分类 ― 这几种危险是当没有适当地隔离时一个事务可能会干涉另一个事务的情况。下列的所有这些危险都与这种情况（ 第二个事务已经启动后第一个事务变得对第二个事务 <i>可见</i>）的结果有关： </p>
		<ul>
				<li>
						<b>脏读（Dirty Read）</b>：当一个事务的中间（未提交的）结果对另一个事务可见时就会发生这种情况。 <br /></li>
				<li>
						<b>不可重复的读（Unrepeatable Read）</b>：当一个事务读取一个数据项，然后重新读取这个数据项并看到不同的值时就是发生了这种情况。 <br /></li>
				<li>
						<b>虚读（Phantom Read）</b>：当一个事务执行返回多个行的查询，稍后再次执行同一个查询并看到第一次执行该查询没出现的额外行时就是发生了这种情况。 </li>
		</ul>
		<p>四个标准隔离级别与这三个隔离危险相关，如表 2 所示。最低的隔离级别“读未提交的”并不能保护事务不被其它事务更改，但它的速度最快，因为它不需要争夺读锁。最高的隔离级别“可串行化的”与上面给出的隔离的定义相当；每个事务好象都与其它事务的影响完全隔离。</p>
		<p>
				<a name="table1">
						<span class="smalltitle">
								<strong>表 2. 事务隔离级别</strong>
						</span>
				</a>
		</p>
		<p>
		</p>
		<table cellspacing="0" cellpadding="3" width="600" border="1">
				<tbody>
						<tr valign="top">
								<td>
										<strong>隔离级别</strong>
								</td>
								<td>
										<b>脏读</b>
								</td>
								<td>
										<b>不可重复的读</b>
								</td>
								<td>
										<b>虚读</b>
								</td>
						</tr>
						<tr>
								<td>读未提交的</td>
								<td>是</td>
								<td>是</td>
								<td>是</td>
						</tr>
						<tr>
								<td>读已提交的</td>
								<td>否</td>
								<td>是</td>
								<td>是</td>
						</tr>
						<tr>
								<td>可重复的读</td>
								<td>否</td>
								<td>否</td>
								<td>是</td>
						</tr>
						<tr>
								<td>可串行化的</td>
								<td>否</td>
								<td>否</td>
								<td>否</td>
						</tr>
				</tbody>
		</table>
		<p>对于大多数数据库，缺省的隔离级别为“读已提交的”，这是个很好的缺省选择，因为它阻止事务在事务中的任何给定的点看到应用程序数据的不一致视图。“读已提交的”是一个很不错的隔离级别，用于大多数典型的短事务，比如获取报表数据或获取要显示给用户的数据的时候（多半是作为 Web 请求的结果），也用于将新数据插入到数据库的情况。</p>
		<p>当您需要所有事务间有较高级别的一致性时，使用较高的隔离级别“可重复的读”和“可串行化的”比较合适，比如在清单 1 示例中，您希望从检查余额以确保有足够的资金到您实际取钱期间账户余额一直保持不变；这就要求至少要用“可重复的读”隔离级别。在数据一致性绝对重要的情况下，比如审核记帐数据库以确保一个帐户的所有借方金额和贷方金额的总数等于它目前的余额时，可能还需要防止创建新行。这种情况下就需要使用“可串行化的”隔离级别。</p>
		<p>最低的隔离级别“读未提交的”很少使用。它适用于您只需要获得近似值，否则查询将导致您不希望的性能开销这种情况。当您想要估计一个变化很快的数量，如定单数或者今天所下定单的总金额（以美元为单位）时一般使用““读未提交的”。</p>
		<p>因为隔离和可伸缩性之间实际是一种此消彼长的关系，所以您在为事务选择隔离级别时应该小心行事。选择太低的级别对数据比较危险。选择太高的级别可能对性能不利，尽管负载比较轻时可能不会这样。一般来说，数据一致性问题比性能问题更严重。如果拿不准，应该以小心为主，选择一个较高的隔离级别。这就引出了规则 5：</p>
		<p>
				<b>规则 5：使用保证数据安全的最低隔离级别，但如果拿不准，请使用“可串行化的”。</b>
		</p>
		<p>即使您打算刚开始时以小心为主并希望结果性能可以接受 ―（被称为“拒绝和祈祷（denial and prayer）”的性能管理技术 ― 很可能是最常用的性能策略，尽管大多数开发者都不承认这一点），在开发组件时考虑隔离需求也是有利的。您应该努力编写能够容忍级别较低但实用的隔离级别的事务，这样，当稍后性能成为问题时，自己就不会陷入困境。因为您需要知道方法正在做什么以及这个方法中隐藏了什么一致性假设来正确设置隔离级别，那么在开发期间仔细说明并发需求和假设，以便在装配应用程序时帮助作出正确的决定也不失为一个好主意。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp0514/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>本文中提供的许多指导可能看起来有点互相矛盾，因为象事务划分和隔离这种问题本来就是此消彼长的。我们正在努力平衡安全性（如果我们不关心安全性，那就压根不必用事务了）和我们用来提供安全限度的工具的性能开销。正确的平衡要依赖许多因素，包括与系统故障或当机时间相关的代价或损害以及组织的风险承受能力。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65591.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:38 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65591.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 动态编译与性能测量</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65589.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:36:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65589.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65589.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65589.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65589.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65589.html</trackback:ping><description><![CDATA[
		<blockquote>为动态编译的语言（例如 Java）编写和解释性能评测，要比为静态编译的语言（例如 C 或 C++）编写困难得多。在这期的 <i>Java 理论与实践</i> 中，Brian Goetz 介绍了动态编译使性能测试复杂的诸多原因中的一些。请在本文附带的讨论组上与作者和其他读者分享您对本文的看法。 (您也可以选择本文顶部或底部的 <b>讨论</b> 访问论坛。) </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>这个月，我着手撰写一篇文章，分析一个写得很糟糕的微评测。毕竟，我们的程序员一直受性能困扰，我们也都想了解我们编写、使用或批评的代码的性能特征。当我偶然间写到性能这个主题时，我经常得到这样的电子邮件：“我写的这个程序显示，动态 frosternation 要比静态 blestification 快，与您上一篇的观点相反!”许多随这类电子邮件而来的所谓“评测“程序，或者它们运行的方式，明显表现出他们对于 JVM 执行字节码的实际方式缺乏基本认识。所以，在我着手撰写这样一篇文章（将在未来的专栏中发表）之前，我们先来看看 JVM 幕后的东西。理解动态编译和优化，是理解如何区分微评测好坏的关键（不幸的是，好的微评测很少）。</p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">动态编译简史</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Java 应用程序的编译过程与静态编译语言（例如 C 或 C++）不同。静态编译器直接把源代码转换成可以直接在目标平台上执行的机器代码，不同的硬件平台要求不同的编译器。 Java 编译器把 Java 源代码转换成可移植的 JVM 字节码，所谓字节码指的是 JVM 的“虚拟机器指令”。与静态编译器不同，javac 几乎不做什么优化 —— 在静态编译语言中应当由编译器进行的优化工作，在 Java 中是在程序执行的时候，由运行时执行。</p>
		<p>第一代 JVM 完全是解释的。JVM 解释字节码，而不是把字节码编译成机器码并直接执行机器码。当然，这种技术不会提供最好的性能，因为系统在执行解释器上花费的时间，比在需要运行的程序上花费的时间还要多。</p>
		<p>
				<a name="1.1">
						<span class="smalltitle">
								<strong>即时编译</strong>
						</span>
				</a>
		</p>
		<p>对于证实概念的实现来说，解释是合适的，但是早期的 JVM 由于太慢，迅速获得了一个坏名声。下一代 JVM 使用即时 (JIT) 编译器来提高执行速度。按照严格的定义，基于 JIT 的虚拟机在执行之前，把所有字节码转换成机器码，但是以惰性方式来做这项工作：JIT 只有在确定某个代码路径将要执行的时候，才编译这个代码路径（因此有了名称“ <i>即时</i> 编译”）。这个技术使程序能启动得更快，因为在开始执行之前，不需要冗长的编译阶段。 </p>
		<p>JIT 技术看起来很有前途，但是它有一些不足。JIT 消除了解释的负担（以额外的启动成本为代价），但是由于若干原因，代码的优化等级仍然是一般般。为了避免 Java 应用程序严重的启动延迟，JIT 编译器必须非常迅速，这意味着它无法把大量时间花在优化上。所以，早期的 JIT 编译器在进行内联假设(inlining assumption)方面比较保守，因为它们不知道后面可能要装入哪个类。</p>
		<p>虽然从技术上讲，基于 JIT 的虚拟机在执行字节码之前，要先编译字节码，但是 JIT 这个术语通常被用来表示任何把字节码转换成机器码的动态编译过程 —— 即使那些能够解释字节码的过程也算。</p>
		<p>
				<a name="1.2">
						<span class="smalltitle">
								<strong>HotSpot 动态编译</strong>
						</span>
				</a>
		</p>
		<p>HotSpot 执行过程组合了编译、性能分析以及动态编译。它没有把所有要执行的字节码转换成机器码，而是先以解释器的方式运行，只编译“热门”代码 —— 执行得最频繁的代码。当 HotSpot 执行时，会搜集性能分析数据，用来决定哪个代码段执行得足够频繁，值得编译。只编译执行最频繁的代码有几项性能优势：没有把时间浪费在编译那些不经常执行的代码上；这样，编译器就可以花更多时间来优化热门代码路径，因为它知道在这上面花的时间物有所值。而且，通过延迟编译，编译器可以访问性能分析数据，并用这些数据来改进优化决策，例如是否需要内联某个方法调用。</p>
		<p>为了让事情变得更复杂，HotSpot 提供了两个编译器：客户机编译器和服务器编译器。默认采用客户机编译器；在启动 JVM 时，您可以指定 <code><font face="Courier" size="2">-server</font></code> 开关，选择服务器编译器。服务器编译器针对最大峰值操作速度进行了优化，适用于需要长期运行的服务器应用程序。客户机编译器的优化目标，是减少应用程序的启动时间和内存消耗，优化的复杂程度远远低于服务器编译器，因此需要的编译时间也更少。 </p>
		<p>HotSpot 服务器编译器能够执行各种样的类。它能够执行许多静态编译器中常见的标准优化，例如代码提升( hoisting)、公共的子表达式清除、循环展开(unrolling)、范围检测清除、死代码清除、数据流分析，还有各种在静态编译语言中不实用的优化技术，例如虚方法调用的聚合内联。</p>
		<p>
				<a name="1.3">
						<span class="smalltitle">
								<strong>持续重新编译</strong>
						</span>
				</a>
		</p>
		<p>HotSpot 技术另一个有趣的方面是：编译不是一个全有或者全无(all-or-nothing)的命题。在解释代码路径一定次数之后，会把它重新编译成机器码。但是 JVM 会继续进行性能分析，而且如果认为代码路径特别热门，或者未来的性能分析数据认为存在额外的优化可能，那么还有可能用更高一级的优化重新编译代码。JVM 在一个应用程序的执行过程中，可能会把相同的字节码重新编译许多次。为了深入了解编译器做了什么，请用 <code><font face="Courier" size="2">-XX:+PrintCompilation</font></code> 标志调用 JVM，这个标志会使编译器（客户机或服务器）每次运行的时候打印一条短消息。 </p>
		<p>
				<a name="1.4">
						<span class="smalltitle">
								<strong>栈上(On-stack)替换</strong>
						</span>
				</a>
		</p>
		<p>HotSpot 开始的版本编译的时候每次编译一个方法。如果某个方法的累计执行次数超过指定的循环迭代次数（在 HotSpot 的第一版中，是 10,000 次），那么这个方法就被当作热门方法，计算的方式是：为每个方法关联一个计数器，每次执行一个后向分支时，就会递增计数器一次。但是，在方法编译之后，方法调用并没有切换到编译的版本，需要退出并重新进入方法，后续调用才会使用编译的版本。结果就是，在某些情况下，可能永远不会用到编译的版本，例如对于计算密集型程序，在这类程序中所有的计算都是在方法的一次调用中完成的。重量级方法可能被编译，但是编译的代码永远用不到。</p>
		<p>HotSpot 最近的版本采用了称为 <i>栈上(on-stack)替换</i> (OSR) 的技术，支持在循环过程中间，从解释执行切换到编译的代码（或者从编译代码的一个版本切换到另一个版本）。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">那么，这与评测有什么关系？</font>
								</strong>
						</span>
				</a>
		</p>
		<p>我向您许诺了一篇关于评测和性能测量的文章，但是迄今为止，您得到的只是历史的教训和 Sun 的 HotSpot 白皮书的老调重谈。绕这么大的圈子的原因是，如果不理解动态编译的过程，就不可能正确地编写或解释 Java 类的性能测试。（即使深入理解动态编译和 JVM 优化，也仍然是非常困难的。）</p>
		<p>
				<a name="2.1">
						<span class="smalltitle">
								<strong>为 Java 代码编写微评测远比为 C 代码编写难得多</strong>
						</span>
				</a>
		</p>
		<p>判断方法 A 是否比方法 B 更快的传统方法，是编写小的评测程序，通常叫做 <i>微评测</i>。这个趋势非常有意义。科学的方法不能缺少独立的调查。魔鬼总在细节之中。为动态编译的语言编写并解释评测，远比为静态编译的语言难得多。为了了解某个结构的性能，编写一个使用该结构的程序一点也没有错，但是在许多情况下，用 Java 编写的微评测告诉您的，往往与您所认为的不一样。 </p>
		<p>使用 C 程序时，您甚至不用运行它，就能了解许多程序可能的性能特征。只要看看编译出的机器码就可以了。编译器生成的指令就是将要执行的机器码，一般情况下，可以很合理地理解它们的时间特征。（有许多有毛病的例子，因为总是遗漏分支预测或缓存，所以性能差的程度远远超过查看机器码所能够想像的程度，但是大多数情况下，您都可以通过查看机器码了解 C 程序的性能的很多方面。）</p>
		<p>如果编译器认为某段代码不恰当，准备把它优化掉（通常的情况是，评测到它实际上不做任何事情），那么您在生成的机器码中可以看到这个优化 —— 代码不在那儿了。通常，对于 C 代码，您不必执行很长时间，就可以对它的性能做出合理的推断。</p>
		<p>而在另一方面，HotSpot JIT 在程序运行时会持续地把 Java 字节码重新编译成机器码，而重新编译触发的次数无法预期，触发重新编译的依据是性能分析数据积累到一定数量、装入新类，或者执行到的代码路径的类已经装入，但是还没有执行过。持续的重新编译情况下的时间测量会非常混乱、让人误解，而且要想获得有用的性能数据，通常必须让 Java 代码运行相当长的时间（我曾经看到过一些怪事，在程序启动运行之后要加速几个小时甚至数天），才能获得有用的性能数据。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">清除死代码</font>
								</strong>
						</span>
				</a>
		</p>
		<p>编写好评测的一个挑战就是，优化编译器要擅长找出死代码 —— 对于程序执行的输出没有作用的代码。但是评测程序一般不产生任何输出，这就意味着有一些，或者全部代码都有可能被优化掉，而毫无知觉，这时您实际测量的执行要少于您设想的数量。具体来说，许多微评测在用 <code><font face="Courier" size="2">-server</font></code> 方式运行时，要比用 <code><font face="Courier" size="2">-client</font></code> 方式运行时好得多，这不是因为服务器编译器更快（虽然服务器编译器一般更快），而是因为服务器编译器更擅长优化掉死代码。不幸的是，能够让您的评测工作非常短（可能会把评测完全优化掉）的死代码优化，在处理实际做些工作的代码时，做得就不会那么好了。 </p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>奇怪的结果</strong>
						</span>
				</a>
		</p>
		<p>清单 1 的评测包含一个什么也不做的代码块，它是从一个测试并发线程性能的评测中摘出来的，但是它实际测量的根本不是要评测的东西。（这个示例是从 JavaOne 2003 的演示 “The Black Art of Benchmarking” 中借用的。请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#resources"><font color="#996699">参考资料</font></a>。） </p>
		<br />
		<a name="listing1">
				<b>清单 1. 被意料之外的死代码弄乱的评测</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
        
public class StupidThreadTest {
    public static void doSomeStuff() {
        double uselessSum = 0;
        for (int i=0; i&lt;1000; i++) {
            for (int j=0;j&lt;1000; j++) {
                uselessSum += (double) i + (double) j;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        doSomeStuff();
        
        int nThreads = Integer.parseInt(args[0]);
        Thread[] threads = new Thread[nThreads];
        for (int i=0; i&lt;nThreads; i++)
            threads[i] = new Thread(new Runnable() {
                public void run() { doSomeStuff(); }
            });
        long start = System.currentTimeMillis();
        for (int i = 0; i &lt; threads.length; i++)
            threads[i].start();
        for (int i = 0; i &lt; threads.length; i++)
            threads[i].join();
        long end = System.currentTimeMillis();
        System.out.println("Time: " + (end-start) + "ms");
    }
}

      </font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>表面上看， <code><font face="Courier" size="2">doSomeStuff()</font></code> 方法可以给线程分点事做，所以我们能够从 <code><font face="Courier" size="2">StupidThreadBenchmark</font></code> 的运行时间推导出多线程调度开支的一些情况。但是，因为 <code><font face="Courier" size="2">uselessSum</font></code> 从没被用过，所以编译器能够判断出 <code><font face="Courier" size="2">doSomeStuff</font></code> 中的全部代码是死的，然后把它们全部优化掉。一旦循环中的代码消失，循环也就消失了，只留下一个空空如也的 <code><font face="Courier" size="2">doSomeStuff</font></code>。表 1 显示了使用客户机和服务器方式执行 <code><font face="Courier" size="2">StupidThreadBenchmark</font></code> 的性能。两个 JVM 运行大量线程的时候，都表现出差不多是线性的运行时间，这个结果很容易被误解为服务器 JVM 比客户机 JVM 快 40 倍。而实际上，是服务器编译器做了更多优化，发现整个 <code><font face="Courier" size="2">doSomeStuff</font></code> 是死代码。虽然确实有许多程序在服务器 JVM 上会提速，但是您在这里看到的提速仅仅代表一个写得糟糕的评测，而不能成为服务器 JVM 性能的证明。但是如果您没有细看，就很容易会把两者混淆。 </p>
		<br />
		<a name="table1">
				<b>表 1. 在客户机和服务器 JVM 中 StupidThreadBenchmark 的性能</b>
		</a>
		<br />
		<table cellspacing="1" cellpadding="3" width="60%" border="0">
				<tbody>
						<tr valign="top">
								<td>线程数量</td>
								<td>客户机 JVM 运行时间</td>
								<td>服务器 JVM 运行时间</td>
						</tr>
						<tr valign="top">
								<td>10</td>
								<td>43</td>
								<td>2</td>
						</tr>
						<tr valign="top">
								<td>100</td>
								<td>435</td>
								<td>10</td>
						</tr>
						<tr valign="top">
								<td>1000</td>
								<td>4142</td>
								<td>80</td>
						</tr>
						<tr valign="top">
								<td>10000</td>
								<td>42402</td>
								<td>1060</td>
						</tr>
				</tbody>
		</table>
		<p>对于评测静态编译语言来说，处理过于积极的死代码清除也是一个问题。但是，在静态编译语言中，能够更容易地发现编译器清除了大块评测。您可以查看生成的机器码，查看是否漏了某块程序。而对于动态编译语言，这些信息不太容易访问得到。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">预热</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果您想测量 X 的性能，一般情况下您是想测量它编译后的性能，而不是它的解释性能（您想知道 X 在赛场上能跑多快）。要做到这样，需要“预热” JVM —— 即让目标操作执行足够的时间，这样编译器在为执行计时之前，就有足够的运行解释的代码，并用编译的代码替换解释代码。</p>
		<p>使用早期 JIT 和没有栈上替换的动态编译器，有一个容易的公式可以测量方法编译后的性能：运行多次调用，启动计时器，然后执行若干次方法。如果预热调用超过方法被编译的阈值，那么实际计时的调用就有可能全部是编译代码执行的时间，所有的编译开支应当在开始计时之前发生。</p>
		<p>而使用今天的动态编译器，事情更困难。编译器运行的次数很难预测，JVM 按照自己的想法从解释代码切换到编译代码，而且在运行期间，相同的代码路径可能编译、重新编译不止一次。如果您不处理这些事件的计时问题，那么它们会严重歪曲您的计时结果。</p>
		<p>图 1 显示了由于预计不到的动态编译而造成的可能的计时歪曲。假设您正在通过循环计时 200,000 次迭代，编译代码比解释代码快 10 倍。如果编译只在 200,000 次迭代时才发生，那么您测量的只是解释代码的性能（时间线(a)）。如果编译在 100,000 次迭代时发生，那么您总共的运行时间是运行 200,000 次解释迭代的时间，加上编译时间（编译时间非您所愿），加上执行 100,000 次编译迭代的时间（时间线(b)）。如果编译在 20,000 次迭代时发生，那么总时间会是 20,000 次解释迭代，加上编译时间，再加上 180,000 次编译迭代（时间线(c)）。因为您不知道编译器什么时候执行，也不知道要执行多长时间，所以您可以看到，您的测量可能受到严重的歪曲。根据编译时间和编译代码比解释代码快的程度，即使对迭代数量只做很小的变化，也可能造成测量的“性能”有极大差异。</p>
		<br />
		<a name="figure1">
				<b>图 1. 因为动态编译计时造成的性能测量歪曲</b>
		</a>
		<br />
		<img alt="时间线图" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/java_diagram.gif" />
		<br />
		<p>那么，到底多少预热才足够呢？您不知道。您能做到的最好的，就是用 <code><font face="Courier" size="2">-XX:+PrintCompilation</font></code> 开关来运行评测，观察什么造成编译器工作，然后改变评测程序的结构，以确保编译在您启动计时之前发生，在计时循环过程中不会再发生编译。 </p>
		<p>
				<a name="4.1">
						<span class="smalltitle">
								<strong>不要忘记垃圾收集</strong>
						</span>
				</a>
		</p>
		<p>那么，您已经看到，如果您想得到正确的计时结果，就必须要让被测代码比您想像的多运行几次，以便让 JVM 预热。另一方面，如果测试代码要进行对象分配工作（差不多所有的代码都要这样），那么垃圾收集器也肯定会运行。这是会严重歪曲计时结果的另一个因素 —— 即使对迭代数量只做很小的变化，也意味着没有垃圾收集和有垃圾收集之间的区别，就会偏离“每迭代时间”的测量。</p>
		<p>如果用 <code><font face="Courier" size="2">-verbose:gc</font></code> 开关运行评测，您可以看到在垃圾收集上耗费了多少时间，并相应地调整您的计时数据。更好一些的话，您可以长时间运行您的程序，这可以保证触发许多垃圾收集，从而更精确地分摊垃圾收集的成本。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5.0">
						<span class="atitle">
								<strong>
										<font size="4">动态反优化(deoptimization)</font>
								</strong>
						</span>
				</a>
		</p>
		<p>许多标准的优化只能在“基本块”内执行，所以内联方法调用对于达到好的优化通常很重要。通过内联方法调用，不仅方法调用的开支被清除，而且给优化器提供了更大的优化块可以优化，会带来相当大的死代码优化机会。</p>
		<p>清单 2 显示了一个通过内联实现的这类优化的示例。 <code><font face="Courier" size="2">outer()</font></code> 方法用参数 <code><font face="Courier" size="2">null</font></code> 调用 <code><font face="Courier" size="2">inner()</font></code>，结果是 <code><font face="Courier" size="2">inner()</font></code> 什么也不做。但是通过把 <code><font face="Courier" size="2">inner()</font></code> 的调用内联，编译器可以发现 <code><font face="Courier" size="2">inner()</font></code> 的 <code><font face="Courier" size="2">else</font></code> 分支是死的，因此能够把测试和 <code><font face="Courier" size="2">else</font></code> 分支优化掉，在某种程度上，它甚至能把整个对 <code><font face="Courier" size="2">inner()</font></code> 的调用全优化掉。如果 <code><font face="Courier" size="2">inner()</font></code> 没有被内联，那么这个优化是不可能发生的。 </p>
		<br />
		<a name="listing2">
				<b>清单 2. 内联如何带来更好的死代码优化</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
        
public class Inline {
  public final void inner(String s) {
    if (s == null)
      return;
    else {
      // do something really complicated
    }
  }

  public void outer() {
    String s=null; 
    inner(s);
  }
}

      </font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但是不方便的是，虚方法对内联造成了障碍，而虚函数调用在 Java 中要比在 C++ 中普遍。假设编译器正试图优化以下代码中对 <code><font face="Courier" size="2">doSomething()</font></code> 的调用： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  Foo foo = getFoo();
  foo.doSomething(); 
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>从这个代码片断中，编译器没有必要分清要执行哪个版本的 <code><font face="Courier" size="2">doSomething()</font></code> —— 是在类 <code><font face="Courier" size="2">Foo</font></code> 中实现的版本，还是在 <code><font face="Courier" size="2">Foo</font></code> 的子类中实现的版本？只在少数情况下答案才明显 —— 例如 <code><font face="Courier" size="2">Foo</font></code> 是 <code><font face="Courier" size="2">final</font></code> 的，或者 <code><font face="Courier" size="2">doSomething()</font></code> 在 <code><font face="Courier" size="2">Foo</font></code> 中被定义为 <code><font face="Courier" size="2">final</font></code> 方法 —— 但是在多数情况下，编译器不得不猜测。对于每次只编译一个类的静态编译器，我们很幸运。但是动态编译器可以使用全局信息进行更好的决策。假设有一个还没有装入的类，它扩展了应用程序中的 <code><font face="Courier" size="2">Foo</font></code>。现在的情景更像是 <code><font face="Courier" size="2">doSomething()</font></code> 是 <code><font face="Courier" size="2">Foo</font></code> 中的 <code><font face="Courier" size="2">final</font></code> 方法 —— 编译器可以把虚方法调用转换成一个直接分配（已经是个改进了），而且，还可以内联 <code><font face="Courier" size="2">doSomething()</font></code>。（把虚方法调用转换成直接方法调用，叫做 <i>单形(monomorphic)调用变换</i>。) </p>
		<p>请稍等 —— 类可以动态装入。如果编译器进行了这样的优化，然后装入了一个扩展了 <code><font face="Courier" size="2">Foo</font></code> 的类，会发生什么？更糟的是，如果这是在工厂方法 <code><font face="Courier" size="2">getFoo()</font></code> 内进行的会怎么样？ <code><font face="Courier" size="2">getFoo()</font></code> 会返回新的 <code><font face="Courier" size="2">Foo</font></code> 子类的实例？那么，生成的代码不就无效了么？对，是无效了。但是 JVM 能指出这个错误，并根据目前无效的假设，取消生成的代码，并恢复解释（或者重新编译不正确的代码路径）。 </p>
		<p>结果就是，编译器要进行主动的内联决策，才能得到更高的性能，然后当这些决策依据的假设不再有效时，就会收回这些决策。实际上，这个优化如此有效，以致于给那些不被覆盖的方法添加 <code><font face="Courier" size="2">final</font></code> 关键字（一种性能技巧，在以前的文章中建议过）对于提高实际性能没有太大作用。 </p>
		<p>
				<a name="4.2">
						<span class="smalltitle">
								<strong>奇怪的结果</strong>
						</span>
				</a>
		</p>
		<p>清单 3 中包含一个代码模式，其中组合了不恰当的预热、单形调用变换以及反优化，因此生成的结果毫无意义，而且容易被误解：</p>
		<br />
		<a name="listing3">
				<b>清单 3. 测试程序的结果被单形调用变换和后续的反优化歪曲</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
        
public class StupidMathTest {
    public interface Operator {
        public double operate(double d);
    }

    public static class SimpleAdder implements Operator {
        public double operate(double d) {
            return d + 1.0;
        }
    }

    public static class DoubleAdder implements Operator {
        public double operate(double d) {
            return d + 0.5 + 0.5;
        }
    }

    public static class RoundaboutAdder implements Operator {
        public double operate(double d) {
            return d + 2.0 - 1.0;
        }
    }

    public static void runABunch(Operator op) {
        long start = System.currentTimeMillis();
        double d = 0.0;
        for (int i = 0; i &lt; 5000000; i++)
            d = op.operate(d);
        long end = System.currentTimeMillis();
        System.out.println("Time: " + (end-start) + "   ignore:" + d);
    }

    public static void main(String[] args) {
        Operator ra = new RoundaboutAdder();
        runABunch(ra); // misguided warmup attempt
        runABunch(ra);
        Operator sa = new SimpleAdder();
        Operator da = new DoubleAdder();
        runABunch(sa);
        runABunch(da);
    }
}

      </font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<code>
						<font face="Courier" size="2">StupidMathTest</font>
				</code> 首先试图做些预热（没有成功），然后测量 <code><font face="Courier" size="2">SimpleAdder</font></code>、 <code><font face="Courier" size="2">DoubleAdder</font></code>、 <code><font face="Courier" size="2">RoundaboutAdder</font></code> 的运行时间，结果如表 2 所示。看起来好像先加 1，再加 2 ，然后再减 1 最快。加两次 0.5 比加 1 还快。这有可能么？（答案是：不可能。） </p>
		<br />
		<a name="table2">
				<b>表 2. StupidMathTest 毫无意义且令人误解的结果</b>
		</a>
		<br />
		<table cellspacing="1" cellpadding="3" width="60%" border="0">
				<tbody>
						<tr valign="top">
								<td>方法</td>
								<td>运行时间</td>
						</tr>
						<tr>
								<td>SimpleAdder</td>
								<td>88ms</td>
						</tr>
						<tr valign="top">
								<td>DoubleAdder</td>
								<td>76ms</td>
						</tr>
						<tr valign="top">
								<td>RoundaboutAdder</td>
								<td>14ms</td>
						</tr>
				</tbody>
		</table>
		<p>这里发生什么呢？在预热循环之后， <code><font face="Courier" size="2">RoundaboutAdder</font></code> 和 <code><font face="Courier" size="2">runABunch()</font></code> 确实已经被编译了，而且编译器 <code><font face="Courier" size="2">Operator</font></code> 和 <code><font face="Courier" size="2">RoundaboutAdder</font></code> 上进行了单形调用转换，第一轮运行得非常快。而在第二轮( <code><font face="Courier" size="2">SimpleAdder</font></code>)中，编译器不得不反优化，又退回虚函数分配之中，所以第二轮的执行表现得更慢，因为不能把虚函数调用优化掉，把时间花在了重新编译上。在第三轮( <code><font face="Courier" size="2">DoubleAdder</font></code>)中，重新编译比第二轮少，所以运行得就更快。（在现实中，编译器会在 <code><font face="Courier" size="2">RoundaboutAdder</font></code> 和 <code><font face="Courier" size="2">DoubleAdder</font></code> 上进行常数替换(constant folding)，生成与 <code><font face="Courier" size="2">SimpleAdder</font></code> 几乎相同的代码。所以如果在运行时间上有差异，那么不是因为算术代码）。哪个代码首先执行，哪个代码就会最快。 </p>
		<p>那么，从这个“评测”中，我们能得出什么结论呢？实际上，除了评测动态编译语言要比您可能想到的要微妙得多之外，什么也没得到。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp12214/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="6.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>这个示例中的结果错得如此明显，所以很清楚，肯定发生了什么，但是更小的结果能够很容易地歪曲您的性能测试程序的结果，却不会触发您的“这里肯定有什么东西有问题”的警惕。虽然本文列出的这些内容是微评测歪曲的一般来源，但是还有许多其他来源。本文的中心思想是：您正在测量的，通常不是您以为您正在测量的。实际上，您通常所测量的，不是您以为您正在测量的。对于那些没有包含什么实际的程序负荷，测试时间不够长的性能测试的结果，一定要非常当心。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65589.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:36 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65589.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 在没有数据库的情况下进行数据库查询</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65590.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:36:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65590.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65590.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65590.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65590.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65590.html</trackback:ping><description><![CDATA[
		<blockquote>手里有锤子的时候，看什么东西都像钉子（就像古谚语所说的那样）。但是如果没有锤子时该怎样办呢？有时，您可以去借一把锤子。然后，拿着这把借来的锤子敲打虚拟的钉子，最后归还锤子，没人知道这些。在本月的<i> Java 理论与实践</i> 系列中，Brian Goetz 将演示如何将 SQL 或者 XQuery 这样的数据操纵之锤应用于非持久存储的数据。请在本文附带的 <a href="javascript:void forumWindow()"><font color="#996699">讨论论坛</font></a> 中与作者和其他读者分享您对本文的看法。（也可以单击本文顶部或底部的 <b>讨论</b> 来访问该论坛。） </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>我最近仔细考察了一个项目，该项目涉及相当多的 Web 快速搜索。当爬虫程序爬过不同的 Web 站点时，它将建立一个数据库，该数据库中包括它所爬过的站点和网页、每一页所包含的链接、每一页的分析结果等数据。最终结果是一组报告，详细说明经过了哪些站点和页面、哪些是一直链接的、哪些链接已经断开、哪些页面有错误、计算出的页面规格，等等。开始的时候，没人确切知道需要什么样的报告，或者应当采用什么样的格式 —— 只知道有一些内容要报告。这表明报告开发阶段会是一个反复的阶段，要经过多次反馈、修改，并且可能尝试使用不同的结构。惟一确定的报告要求是，报告应当以 XML 形式展示，也可能以 HTML 形式展示。因此，开发和修改报告的过程必须是轻量级的，因为报告要求是“动态发现”的，而不是预先指定的。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">不需要数据库</font>
								</strong>
						</span>
				</a>
		</p>
		<p>对这个问题的“最显而易见的”解决方法是将所有东西都放入 SQL 数据库中 —— 页面、链接、度量标准、HTTP 结果代码、计时结果和其他元数据。这个问题可以借助关系表示来很好地解决，特别是因为这种方法不需要存储已访问页面的内容，只需要存储它们的结构和元数据。</p>
		<p>到目前为止，这个项目看起来像是一个典型的数据库应用程序，并且它并不缺少可供选择的持久性策略。但是，或许可以避免使用数据库持久存储数据的复杂性 —— 这个快速搜索工具（crawler）只访问数万个页面。这个数字不是很大，因此可以将整个数据库放在内存中，当需要持久存储数据时，可以通过序列化来实现它。（是的，加载和保存操作要花费较长的时间，但是这些操作并不经常执行。）懒惰反而带来了一个好处 —— 不需要处理持久性极大地缩短了开发应用程序的时间，因而显著地减少了开发工作量。构建和操纵内存中的数据结构要比每次添加、提取或者分析数据时都使用数据库容易得多。不管选择了哪种持久存储模型，都会限制任何触及到数据的代码的构造。</p>
		<p>内存中的数据结构是一种树型结构，如清单 1 所示，它的根是快速搜索过的各个网站的主页，因此 Visitor 模式是搜索这些主页或者从中提取数据的理想模式。（构建一个防止陷入链接循环 —— A 链接到 B、B 链接到 C、C 链接到 A —— 的基本 Visitor 类并不是很难。） </p>
		<br />
		<a name="code1">
				<b>清单 1. Web 爬行器的一个简化方案</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class Site {
    Page homepage;
    Collection&lt;Page&gt; pages;
    Collection&lt;Link&gt; links;
}

public class Page {
    String url;
    Site site;
    PageMetrics metrics;
}

public class Link {
    Page linkFrom;
    Page linkTo;
    String anchorText;
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这个快速搜索工具的应用程序中有十多个 Visitor，它们所做的事情类似于选择页面做进一步分析、选择不带链接的页面、列出“被链接最多”的页面，等等。因为所有这些操作都很简单，所以 Visitor 模式（如清单 2 所示）可以工作得很好，由于数据结构可以放到内存中，因此就算进行彻底搜索，花费也不是很大： </p>
		<br />
		<a name="code2">
				<b>清单 2. 用于 Web 快速搜索工具数据库的 Visitor 模式 </b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public interface Visitor {
    public void visitSite(Site site);
    public void visitLink(Link link);
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="1.1">
						<span class="smalltitle">
								<strong>噢，忘记报告了</strong>
						</span>
				</a>
		</p>
		<p>如果不运行报告的话，Visitor 策略在访问数据方面会做得非常好。使用数据库进行持久存储的一个好处是：在生成报告时，SQL 的能力就会大放光彩 —— 几乎可以让数据库做任何事情。甚至用 SQL 生成报告原型也很容易 —— 运行原型报告，如果结果不是所需要的结果，那么可以修改 SQL 查询或者编写新的查询，然后再试一试。如果改变的只是 SQL 查询的话，那么这个编辑-编译-运行周期可能很快。如果 SQL 不是存储在程序中，那么您甚至可以跳过这个周期的编译部分，这样可以快速生成报告的原型。确定所需要的报告后，将它们构建到应用程序中就很容易了。 </p>
		<p>因此，虽然对于添加新结果、寻找特定的结果和进行特殊传输来说，内存中的数据结构都表现得很不错，但是对于报告来说，这些变成了不利条件。对于所有其自身结构与数据库结构不同的报告，Visitor 都必须创建一个全新的数据结构，以包含报告数据。因此，每一种报告类型都需要有自己的、特定于报告的中间数据结构来存放结果，还需要一个用来填充中间数据结构的访问者，以及用来将中间数据结构转换成最终报告的后处理（post-processing）代码。似乎需要做很多工作，尤其在大多数原型报告将被抛弃时。例如，假定您想要列出所有从其他网站链接到某个给定网站的页面的报告、所有外部页面的列表报告，以及站点上链接该页面的那些页面的列表，然后，根据链接的数量对报告进行归类，链接最多的页面显示在最前面。这个计划基本上将数据结构从里到外翻了个个儿。为了用 Visitor 实现这种数据转换，需要获得从某个给定网站可以到达的外部页面链接的列表，并根据被链接的页面对它们进行分类，如清单 3 所示： </p>
		<br />
		<a name="code3">
				<b>清单 3. Visitor 列出被链接最多的页面，以及链接到它们的页面</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class InvertLinksVisitor {
    public Map&lt;Page, Set&lt;Page&gt;&gt; map = ...;
    
    public void visitLink(Link link) {
        if (link.linkFrom.site.equals(targetSite) 
            &amp;&amp; !link.linkTo.site.equals(targetSite)) {
            if (!map.containsKey(link.linkTo))
                map.put(link.linkTo, new HashSet&lt;Page&gt;());
            map.get(link.linkTo).add(link.linkFrom);
        }
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>清单 3 中的 Visitor 生成一个映射，将每一个外部页面与链接它的一组内部页面相关联。为了准备该报告，还必须根据关联页面的大小对这些条目进行分类，然后创建报告。虽然没有任何困难步骤，但是每一个报告需要的特定于报告的代码数量却很多，因此快速报告原型就成为一个重要的目标（因为没有提出报告要求），试验新报告的开销比理想情况更高。许多报告需要多次传递数据，以便对数据进行选择、汇总和分类。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05315.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">我的数据模型王国</font>
								</strong>
						</span>
				</a>
		</p>
		<p>这时，缺少一个正式的数据模型开始成为一项不利因素，该数据模型可以用于描述收集的数据，并且可以用它更容易地表示选择和聚合查询。也许懒惰不像开始希望的那样有效。但是，虽然这个应用程序缺少正式数据模型，但也许我们可以将数据存储到内存中的数据库，并凭借该数据库进行查询，通过这种方式借用一个数据模型。有两种可能会立即出现在您的脑海中：开源的内存中的 SQL 数据库 HSQLDB 和 XQuery。我不需要数据库提供的持久性，但是我确实需要查询语言。 </p>
		<p>HSQLDB 是一个用 Java 语言编写的可嵌入的数据库引擎。它既包含适用于内存中表的表类型，又包含适用于基于磁盘的表的表类型，设计该引擎为了将表完全嵌入到应用程序中，消除与大多数真实数据库相关的管理开销。要将数据装载到 HSQLDB，只需编写一个 Visitor 即可，该 Visitor 将遍历内存中的数据结构，并为每一个将要存储的实体生成相应的 INSERT 语句。然后可以对这个内存中的数据库表执行 SQL 查询，以生成报告，并在完成这些操作后抛弃这个“数据库”。 </p>
		<p>
				<a name="2.1">
						<span class="smalltitle">
								<strong>噢，忘记了关系数据库有多烦人</strong>
						</span>
				</a>
		</p>
		<p>HSQLDB 方法是一个可行方法，但您很快就发现，我必须为对象关系的不匹配而两次（而不是一次）受罚 —— 一次是在将树型结构数据库转换为关系数据模型时，一次是在将平面关系查询结果转换成结构化的 XML 或者 HTML 结果集时。此外，将 JDBC ResultSet 后处理为 DOM 表示形式的 XML 或者 HTML 文档也不是一项很容易的任务，需要为每一个报告提供一些定制的编码。因此虽然内存中的 SQL 数据库 <i>的确</i> 可以简化查询，但是从数据库中存入和取出数据所需要的额外代码会抵消所有节省的代码。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05315.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">让 XQuery 来拯救您</font>
								</strong>
						</span>
				</a>
		</p>
		<p>另一个容易得到的数据查询方法是 XQuery。XQuery 的优点是，它是为生成 XML 或者 HTML 文档作为查询结果而设计的，因此不需要对查询结果进行后处理。这种想法很有吸引力 —— 每个报告只有一层编码，而不是两层或者更多层。因此第一项任务是构建一个表示整个数据集的 XML 文档。设计一个简单的 XML 数据模型和编写遍历数据结构，并将每一个元素附加到一个 DOM 文档中的 Visitor 很简单。（不需要写出这个文档。可以将它保持在内存中，用于查询，然后在完成查询时丢弃它。当底层数据改变时，可以重新生成它。）之后，所有要做的就是编写 XQuery 查询，该查询将选择并聚集用于报告的数据，并按最终需要的格式（XML 或 HTML）对它们进行格式化。查询可以存储在单独的文件中，以便进行快速原型制造，因此，可支持多种报告格式。使用 Saxon 评估查询的代码如清单 4 中所示： </p>
		<br />
		<a name="code4">
				<b>清单 4. 执行 XQuery 查询并将结果序列化为 XML 或 HTML 文档的代码</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
  String query = readFile(queryFile + ".xq");
  Configuration c = new Configuration();
  StaticQueryContext qp = new StaticQueryContext(c);
  XQueryExpression xe = qp.compileQuery(query);
  DynamicQueryContext dqc = new DynamicQueryContext(c);
  dqc.setContextNode(new DocumentWrapper(document, z.getName(), c));
  List result = xe.evaluate(dqc);

  FileOutputStream os = new FileOutputStream(fileName);
  XMLSerializer serializer = new XMLSerializer (os, format);
  serializer.asDOMSerializer();

  for(Iterator i = result.iterator(); i.hasNext(); ) {
      Object o = i.next();
      if (o instanceof Element)
          serializer.serialize((Element) o);
      else if (o instanceof Attr) {
          Element e = document.createElement("scalar");
          e.setTextContent(((Attr) o).getNodeValue());
          serializer.serialize(e);
      }
      else {
          Element e = document.createElement("scalar");
          e.setTextContent(o.toString());
          serializer.serialize(e);
      }
  }
  os.close(); 
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>表示数据库的 XML 文档的结构与内存中的数据结构稍有不同，每一个 &lt;site&gt; 元素都有嵌套的 &lt;page&gt; 元素，每一个 &lt;page&gt; 元素都有嵌套的 &lt;link&gt; 元素，而每一个 &lt;link&gt; 元素都有 &lt;link-to&gt; 和 &lt;link-from&gt; 元素。实践证明，这种表示方法对于大多数报告都很方便。 </p>
		<p>清单 5 显示了一个示例 XQuery 报告，这个报告处理链接的选择、分类和表示。它有几个地方优于 Visitor 方法 —— 不仅代码少（因为查询语言支持选择、聚积和分类），而且所有报告的代码 —— 选择、聚积、分类和表示 —— 都在一个位置上。 </p>
		<br />
		<a name="code5">
				<b>清单 5.生成链接次数最多的页面的完整报告的 XQuery 代码</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
&lt;html&gt;
&lt;head&gt;&lt;title&gt;被链接最多的页面&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;ul&gt;
{
  let $links := //link[link-to/@siteUrl ne $targetSite
                       and link-from/@siteUrl eq $targetSite]
  for $page in distinct-values($links/link-to/@url)
  let $linkingPages := $links[link-to/@url eq $page]/link-from/@url
  order by count($linkingPages)
  return 
    &lt;li&gt;Page {$page}, {count($linkingPages)} links 
    &lt;ul&gt; {
      for $p in $linkingPages return &lt;li&gt;Linked from {$p/@url}&lt;/li&gt;
    }
    &lt;/ul&gt;&lt;/li&gt;
}
&lt;/ul&gt; &lt;/body&gt; &lt;/html&gt;
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp05315.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>从开发成本角度看，XQuery 方法已证实可以节约大量成本。树型结构对于构建和搜索数据很理想，但对于报告，就不是很理想了。XML 方法很适合于报告（因为可以利用 XQuery 的能力），但是对于整个应用程序的实现，该方法还有很多不便，并会降低性能。因为数据集的大小是可管理的 —— 只有几十兆字节，所以可以将数据从一种格式转换为从开发的角度看最方便的另一种格式。更大的数据集，比如不能完全存储到内存中的数据集，会要求整个应用程序都围绕着一个数据库构建。虽然有许多处理数据持久性的好工具，但是它们需要的工作都比简单操纵内存中数据结构要多得多。如果数据集的大小合适，那么就可以同时利用这两种方法的长处。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65590.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:36 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65590.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 用动态代理进行修饰</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65587.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:35:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65587.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65587.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65587.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65587.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65587.html</trackback:ping><description><![CDATA[
		<blockquote>
				<i>动态代理工具</i> 是 <code><font face="Courier" size="2">java.lang.reflect</font></code> 包的一部分，在 JDK 1.3 版本中添加到 JDK，它允许程序创建 <i>代理对象</i>，代理对象能实现一个或多个已知接口，并用反射代替内置的虚方法分派，编程地分派对接口方法的调用。这个过程允许实现“截取”方法调用，重新路由它们或者动态地添加功能。本期文章中，Brian Goetz 介绍了几个用于动态代理的应用程序。请在本文伴随的 <a href="javascript:void forumWindow()"><font color="#996699">讨论论坛</font></a> 上与作者和其他读者分享您对这篇文章的想法。（也可以单击文章顶部或底部的 <b>讨论</b> 访问讨论论坛。）</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>动态代理为实现许多常见设计模式（包括 Facade、Bridge、Interceptor、Decorator、Proxy（包括远程和虚拟代理）和 Adapter 模式）提供了替代的动态机制。虽然这些模式不使用动态代理，只用普通的类就能够实现，但是在许多情况下，动态代理方式更方便、更紧凑，可以清除许多手写或生成的类。</p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">Proxy 模式</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Proxy 模式中要创建“stub”或“surrogate”对象，它们的目的是接受请求并把请求转发到实际执行工作的其他对象。远程方法调用（RMI）利用 Proxy 模式，使得在其他 JVM 中执行的对象就像本地对象一样；企业 JavaBeans （EJB）利用 Proxy 模式添加远程调用、安全性和事务分界；而 JAX-RPC Web 服务则用 Proxy 模式让远程服务表现得像本地对象一样。在每一种情况中，潜在的远程对象的行为是由接口定义的，而接口本质上接受多种实现。调用者（在大多数情况下）不能区分出它们只是持有一个对 stub 而不是实际对象的引用，因为二者实现了相同的接口；stub 的工作是查找实际的对象、封送参数、把参数发送给实际对象、解除封送返回值、把返回值返回给调用者。代理可以用来提供远程控制（就像在 RMI、EJB 和 JAX-RPC 中那样），用安全性策略包装对象（EJB）、为昂贵的对象（EJB 实体 Bean）提供惰性装入，或者添加检测工具（例如日志记录）。</p>
		<p>在 5.0 以前的 JDK 中，RMI stub（以及它对等的 skeleton）是在编译时由 RMI 编译器（rmic）生成的类，RMI 编译器是 JDK 工具集的一部分。对于每个远程接口，都会生成一个 stub（代理）类，它代表远程对象，还生成一个 skeleton 对象，它在远程 JVM 中做与 stub 相反的工作 —— 解除封送参数并调用实际的对象。类似地，用于 Web 服务的 JAX-RPC 工具也为远程 Web 服务生成代理类，从而使远程 Web 服务看起来就像本地对象一样。</p>
		<p>不管 stub 类是以源代码还是以字节码生成的，代码生成仍然会向编译过程添加一些额外步骤，而且因为命名相似的类的泛滥，会带来意义模糊的可能性。另一方面，动态代理机制支持在编译时没有生成 stub 类的情况下，在运行时创建代理对象。在 JDK 5.0 及以后版本中，RMI 工具使用动态代理代替了生成的 stub，结果 RMI 变得更容易使用。许多 J2EE 容器也使用动态代理来实现 EJB。EJB 技术严重地依靠使用拦截（interception）来实现安全性和事务分界；动态代理为接口上调用的所有方法提供了集中的控制流程路径。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">动态代理机制</font>
								</strong>
						</span>
				</a>
		</p>
		<p>动态代理机制的核心是 <code><font face="Courier" size="2">InvocationHandler</font></code> 接口，如清单 1 所示。调用句柄的工作是代表动态代理实际执行所请求的方法调用。传递给调用句柄一个 <code><font face="Courier" size="2">Method</font></code> 对象（从 <code><font face="Courier" size="2">java.lang.reflect</font></code> 包），参数列表则传递给方法；在最简单的情况下，可能仅仅是调用反射性的方法 <code><font face="Courier" size="2">Method.invoke()</font></code> 并返回结果。</p>
		<br />
		<a name="listing1">
				<b>清单 1. InvocationHandler 接口</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>每个代理都有一个与之关联的调用句柄，只要代理的方法被调用时就会调用该句柄。根据通用的设计原则：接口定义类型、类定义实现，代理对象可以实现一个或多个接口，但是不能实现类。因为代理类没有可以访问的名称，它们不能有构造函数，所以它们必须由工厂创建。清单 2 显示了动态代理的最简单的可能实现，它实现 <code><font face="Courier" size="2">Set</font></code> 接口并把所有 <code><font face="Courier" size="2">Set</font></code> 方法（以及所有 Object 方法）分派给封装的 <code><font face="Courier" size="2">Set</font></code> 实例。</p>
		<br />
		<a name="listing2">
				<b>清单 2. 包装 Set 的简单的动态代理</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class SetProxyFactory {

    public static Set getSetProxy(final Set s) {
        return (Set) Proxy.newProxyInstance
          (s.getClass().getClassLoader(),
                new Class[] { Set.class },
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, 
                      Object[] args) throws Throwable {
                        return method.invoke(s, args);
                    }
                });
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<code>
						<font face="Courier" size="2">SetProxyFactory</font>
				</code>类包含一个静态工厂方法 <code><font face="Courier" size="2">getSetProxy()</font></code>，它返回一个实现了 <code><font face="Courier" size="2">Set</font></code> 的动态代理。代理对象实际实现 <code><font face="Courier" size="2">Set</font></code> —— 调用者无法区分（除非通过反射）返回的对象是动态代理。<code><font face="Courier" size="2">SetProxyFactory</font></code> 返回的代理只做一件事，把方法分派给传递给工厂方法的 <code><font face="Courier" size="2">Set</font></code> 实例。虽然反射代码通常比较难读，但是这里的内容很少，跟上控制流程并不难 —— 只要某个方法在 <code><font face="Courier" size="2">Set</font></code> 代理上被调用，它就被分派给调用句柄，调用句柄只是反射地调用底层包装的对象上的目标方法。当然，绝对什么都不做的代理可能有点傻，是不是呢？</p>
		<p>
				<a name="2.1">
						<span class="smalltitle">
								<strong>什么都不做的适配器</strong>
						</span>
				</a>
		</p>
		<p>对于像 <code><font face="Courier" size="2">SetProxyFactory</font></code> 这样什么都不做的包装器来说，实际有个很好的应用 —— 可以用它安全地把对象引用的范围缩小到特定接口（或接口集）上，方式是，调用者不能提升引用的类型，使得可以更安全地把对象引用传递给不受信任的代码（例如插件或回调）。清单 3 包含一组类定义，实现了典型的回调场景。从中会看到动态代理可以更方便地替代通常用手工（或用 IDE 提供的代码生成向导）实现的 Adapter 模式。</p>
		<br />
		<a name="listing3">
				<b>清单 3. 典型的回调场景</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public interface ServiceCallback {
    public void doCallback();
}

public interface Service {
    public void serviceMethod(ServiceCallback callback);
}

public class ServiceConsumer implements ServiceCallback {
    private Service service;

    ...
    public void someMethod() {
        ...
        service.serviceMethod(this);
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<code>
						<font face="Courier" size="2">ServiceConsumer</font>
				</code>类实现了 <code><font face="Courier" size="2">ServiceCallback</font></code>（这通常是支持回调的一个方便途径）并把 <code><font face="Courier" size="2">this</font></code> 引用传递给 <code><font face="Courier" size="2">serviceMethod()</font></code> 作为回调引用。这种方法的问题是没有机制可以阻止 <code><font face="Courier" size="2">Service</font></code> 实现把 <code><font face="Courier" size="2">ServiceCallback</font></code> 提升为 <code><font face="Courier" size="2">ServiceConsumer</font></code>，并调用 <code><font face="Courier" size="2">ServiceConsumer</font></code> 不希望 <code><font face="Courier" size="2">Service</font></code> 调用的方法。有时对这个风险并不关心 —— 但有时却关心。如果关心，那么可以把回调对象作为内部类，或者编写一个什么都不做的适配器类（请参阅清单 4 中的 <code><font face="Courier" size="2">ServiceCallbackAdapter</font></code>）并用 <code><font face="Courier" size="2">ServiceCallbackAdapter</font></code> 包装 <code><font face="Courier" size="2">ServiceConsumer</font></code>。<code><font face="Courier" size="2">ServiceCallbackAdapter</font></code> 防止 <code><font face="Courier" size="2">Service</font></code> 把 <code><font face="Courier" size="2">ServiceCallback</font></code> 提升为 <code><font face="Courier" size="2">ServiceConsumer</font></code>。</p>
		<br />
		<a name="listing4">
				<b>清单 4. 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class ServiceCallbackAdapter implements ServiceCallback {
    private final ServiceCallback cb;

    public ServiceCallbackAdapter(ServiceCallback cb) {
        this.cb = cb;
    }

    public void doCallback() {
        cb.doCallback();
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>编写 <code><font face="Courier" size="2">ServiceCallbackAdapter</font></code> 这样的适配器类简单却乏味。必须为包装的接口中的每个方法编写重定向类。在 <code><font face="Courier" size="2">ServiceCallback</font></code> 的示例中，只有一个需要实现的方法，但是某些接口，例如 Collections 或 JDBC 接口，则包含许多方法。现代的 IDE 提供了“Delegate Methods”向导，降低了编写适配器类的工作量，但是仍然必须为每个想要包装的接口编写一个适配器类，而且对于只包含生成的代码的类，也有一些让人不满意的地方。看起来应当有一种方式可以更紧凑地表示“什么也不做的限制适配器模式”。</p>
		<p>
				<a name="2.2">
						<span class="smalltitle">
								<strong>通用适配器类</strong>
						</span>
				</a>
		</p>
		<p>
				<a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#listing2">
						<font color="#996699">清单 2</font>
				</a>中的 <code><font face="Courier" size="2">SetProxyFactory</font></code> 类当然比用于 <code><font face="Courier" size="2">Set</font></code> 的等价的适配器类更紧凑，但是它仍然只适用于一个接口：<code><font face="Courier" size="2">Set</font></code>。但是通过使用泛型，可以容易地创建通用的代理工厂，由它为任何接口做同样的工作，如清单 5 所示。它几乎与 <code><font face="Courier" size="2">SetProxyFactory</font></code> 相同，但是可以适用于任何接口。现在再也不用编写限制适配器类了！如果想创建代理对象安全地把对象限制在接口 <code><font face="Courier" size="2">T</font></code>，只要调用 <code><font face="Courier" size="2">getProxy(T.class,object)</font></code> 就可以了，不需要一堆适配器类的额外累赘。</p>
		<br />
		<a name="listing5">
				<b>清单 5. 通用的限制适配器工厂类</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class GenericProxyFactory {

    public static&lt;T&gt; T getProxy(Class&lt;T&gt; intf, 
      final T obj) {
        return (T) 
          Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                new Class[] { intf },
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, 
                      Object[] args) throws Throwable {
                        return method.invoke(obj, args);
                    }
                });
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">动态代理作为 Decorator</font>
								</strong>
						</span>
				</a>
		</p>
		<p>当然，动态代理工具能做的，远不仅仅是把对象类型限制在特定接口上。从 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#listing2"><font color="#996699">清单 2</font></a> 和 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#listing5"><font color="#996699">清单 5</font></a> 中简单的限制适配器到 Decorator 模式，是一个小的飞跃，在 Decorator 模式中，代理用额外的功能（例如安全检测或日志记录）包装调用。清单 6 显示了一个日志 <code><font face="Courier" size="2">InvocationHandler</font></code>，它在调用目标对象上的方法之外，还写入一条日志信息，显示被调用的方法、传递的参数，以及返回值。除了反射性的 <code><font face="Courier" size="2">invoke()</font></code> 调用之外，这里的全部代码只是生成调试信息的一部分 —— 还不是太多。代理工厂方法的代码几乎与 <code><font face="Courier" size="2">GenericProxyFactory</font></code> 相同，区别在于它使用的是 <code><font face="Courier" size="2">LoggingInvocationHandler</font></code> 而不是匿名的调用句柄。</p>
		<br />
		<a name="listing6">
				<b>清单 6. 基于代理的 Decorator，为每个方法调用生成调试日志</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
    private static class LoggingInvocationHandler&lt;T&gt; 
      implements InvocationHandler {
        final T underlying;

        public LoggingHandler(T underlying) {
            this.underlying = underlying;
        }

        public Object invoke(Object proxy, Method method, 
          Object[] args) throws Throwable {
            StringBuffer sb = new StringBuffer();
            sb.append(method.getName()); sb.append("(");
            for (int i=0; args != null &amp;&amp; i&lt;args.length; i++) {
                if (i != 0)
                    sb.append(", ");
                sb.append(args[i]);
            }
            sb.append(")");
            Object ret = <span class="boldcode"><strong>method.invoke</strong></span>(underlying, args);
            if (ret != null) {
                sb.append(" -&gt; "); sb.append(ret);
            }
            System.out.println(sb);
            return ret;
        }
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果用日志代理包装 <code><font face="Courier" size="2">HashSet</font></code>，并执行下面这个简单的测试程序：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    Set s = newLoggingProxy(Set.class, new HashSet());
    s.add("three");
    if (!s.contains("four"))
        s.add("four");
    System.out.println(s);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>会得到以下输出：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">  add(three) -&gt; true
  contains(four) -&gt; false
  add(four) -&gt; true
  toString() -&gt; [four, three]
  [four, three]
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这种方式是给对象添加调试包装器的一种好的而且容易的方式。它当然比生成代理类并手工创建大量 <code><font face="Courier" size="2">println()</font></code> 语句容易得多（也更通用）。我进一步改进了这一方法；不必无条件地生成调试输出，相反，代理可以查询动态配置存储（从配置文件初始化，可以由 JMX <code><font face="Courier" size="2">MBean</font></code> 动态修改），确定是否需要生成调试语句，甚至可能在逐个类或逐个实例的基础上进行。</p>
		<p>在这一点上，我认为读者中的 AOP 爱好者们几乎要跳出来说“这正是 AOP 擅长的啊！”是的，但是解决问题的方法不止一种 —— 仅仅因为某项技术能解决某个问题，并不意味着它就是最好的解决方案。在任何情况下，动态代理方式都有完全在“纯 Java”范围内工作的优势，不是每个公司都用（或应当用） AOP 的。</p>
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>动态代理作为适配器</strong>
						</span>
				</a>
		</p>
		<p>代理也可以用作真正的适配器，提供了对象的一个视图，导出与底层对象实现的接口不同的接口。调用句柄不需要把每个方法调用都分派给相同的底层对象；它可以检查名称，并把不同的方法分派给不同的对象。例如，假设有一组表示持久实体（<code><font face="Courier" size="2">Person</font></code>、<code><font face="Courier" size="2">Company</font></code> 和 <code><font face="Courier" size="2">PurchaseOrder</font></code>） 的 JavaBean 接口，指定了属性的 getter 和 setter，而且正在编写一个持久层，把数据库记录映射到实现这些接口的对象上。现在不用为每个接口编写或生成类，可以只用一个 JavaBean 风格的通用代理类，把属性保存在 Map 中。</p>
		<p>清单 7 显示的动态代理检查被调用方法的名称，并通过查询或修改属性图直接实现 getter 和 setter 方法。现在，这一个代理类就能实现多个 JavaBean 风格接口的对象。</p>
		<br />
		<a name="listing7">
				<b>清单 7. 用于把 getter 和 setter 分派给 Map 的动态代理类</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">				
public class JavaBeanProxyFactory {
    private static class JavaBeanProxy implements InvocationHandler {
        Map&lt;String, Object&gt; properties = new HashMap&lt;String, 
          Object&gt;();

        public JavaBeanProxy(Map&lt;String, Object&gt; properties) {
            this.properties.putAll(properties);
        }

        public Object invoke(Object proxy, Method method, 
          Object[] args) 
          throws Throwable {
            String meth = method.getName();
            if (meth.startsWith("get")) {
                String prop = meth.substring(3);
                Object o = properties.get(prop);
                if (o != null &amp;&amp; !method.getReturnType().isInstance(o))
                    throw new ClassCastException(o.getClass().getName() + 
                      " is not a " + method.getReturnType().getName());
                return o;
            }
            else if (meth.startsWith("set")) {
                // Dispatch setters similarly
            }
            else if (meth.startsWith("is")) {
                // Alternate version of get for boolean properties
            }
            else {
                // Can dispatch non get/set/is methods as desired
            }
        }
    }

    public static&lt;T&gt; T getProxy(Class&lt;T&gt; intf,
      Map&lt;String, Object&gt; values) {
        return (T) Proxy.newProxyInstance
          (JavaBeanProxyFactory.class.getClassLoader(),
                new Class[] { intf }, new JavaBeanProxy(values));
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>虽然因为反射在 <code><font face="Courier" size="2">Object</font></code> 上工作会有潜在的类型安全性上的损失，但是，JavaBeanProxyFactory 中的 getter 处理会进行一些必要的额外的类型检测，就像我在这里用 <code><font face="Courier" size="2">isInstance()</font></code> 对 getter 进行的检测一样。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp08305.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">性能成本</font>
								</strong>
						</span>
				</a>
		</p>
		<p>正如已经看到的，动态代理拥有简化大量代码的潜力 —— 不仅能替代许多生成的代码，而且一个代理类还能代替多个手写的类或生成的代码。什么是成本呢？ 因为反射地分派方法而不是采用内置的虚方法分派，可能有一些性能上的成本。在早期的 JDK 中，反射的性能很差（就像早期 JDK 中几乎其他每件事的性能一样），但是在近 10 年，反射已经变得快多了。</p>
		<p>不必进入基准测试构造的主题，我编写了一个简单的、不太科学的测试程序，它循环地把数据填充到 <code><font face="Courier" size="2">Set</font></code>，随机地对 <code><font face="Courier" size="2">Set</font></code>进行插入、查询和删除元素。我用三个 <code><font face="Courier" size="2">Set</font></code> 实现运行它：一个未经修饰的 <code><font face="Courier" size="2">HashSet</font></code>，一个手写的、只是把所有方法转发到底层的 <code><font face="Courier" size="2">HashSet</font></code> 的 <code><font face="Courier" size="2">Set</font></code> 适配器，还有一个基于代理的、也只是把所有方法转发到底层 <code><font face="Courier" size="2">HashSet</font></code> 的 <code><font face="Courier" size="2">Set</font></code> 适配器。每次循环迭代都生成若干随机数，并执行一个或多个 <code><font face="Courier" size="2">Set</font></code> 操作。手写的适配器比起原始的 <code><font face="Courier" size="2">HashSet</font></code> 只产生很少百分比的性能负荷（大概是因为 JVM 级有效的内联缓冲和硬件级的分支预测）；代理适配器则明显比原始 <code><font face="Courier" size="2">HashSet</font></code> 慢，但是开销要少于两个量级。</p>
		<p>我从这个试验得出的结论是：对于大多数情况，代理方式即使对轻量级方法也执行得足够好，而随着被代理的操作变得越来越重量级（例如远程方法调用，或者使用序列化、执行 IO 或者从数据库检索数据的方法），代理开销就会有效地接近于 0。当然也存在一些代理方式的性能开销无法接受的情况，但是这些通常只是少数情况。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65587.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:35 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65587.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java理论与实践: 它是谁的对象？</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65585.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:33:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65585.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65585.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65585.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65585.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65585.html</trackback:ping><description><![CDATA[
		<blockquote>在没有垃圾收集的语言中，比如C++，必须特别关注内存管理。对于每个动态对象，必须要么实现引用计数以模拟 垃圾收集效果，要么管理每个对象的“所有权”――确定哪个类负责删除一个对象。通常，对这种所有权的维护并没有什么成文的规则，而是按照约定（通常是不成文的）进行维护。尽管垃圾收集意味着Java开发者不必太多地担心内存 泄漏，有时我们仍然需要担心对象所有权，以防止数据争用（data races）和不必要的副作用。在这篇文章中，Brian Goetz 指出了一些这样的情况，即Java开发者必须注意对象所有权。请在 <a href="javascript:void forumWindow()"><font color="#996699">论坛</font></a>上与作者及其他读者共享您对本文的一些想法（您也可以在文章的顶部或底部点击 <b>讨论</b>来访问论坛）。 </blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>如果您是在1997年之前开始学习编程，那么可能您学习的第一种编程语言没有提供透明的垃圾收集。每一个new 操作必须有相应的delete操作 ，否则您的程序就会泄漏内存，最终内存分配器（memory allocator ）就会出故障，而您的程序就会崩溃。每当利用 new 分配一个对象时，您就得问自己，谁将删除该对象？何时删除？</p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">别名， 也叫做 ...</font>
								</strong>
						</span>
				</a>
		</p>
		<p>内存管理复杂性的主要原因是别名使用：同一块内存或对象具有 多个指针或引用。别名在任何时候都会很自然地出现。例如，在清单 1 中，在 makeSomething 的第一行创建的 Something 对象至少有四个引用：</p>
		<ul>
				<li>something 引用。 
</li>
				<li>集合 c1 中至少有一个引用。 
</li>
				<li>当 something 被作为参数传递给 registerSomething 时，会创建临时 aSomething 引用。 
</li>
				<li>集合 c2 中至少有一个引用。 </li>
		</ul>
		<br />
		<a name="IDAFMYFC">
				<b>清单 1. 典型代码中的别名</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    Collection c1, c2;
    
    public void makeSomething {
        Something something = new Something();
        c1.add(something);
        registerSomething(something);
    }

    private void registerSomething(Something aSomething) {
        c2.add(aSomething);
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在非垃圾收集语言中需要避免两个主要的内存管理危险：内存泄漏和悬空指针。为了防止内存泄漏，必须确保每个分配了内存的对象最终都会被删除。 为了避免悬空指针（一种危险的情况，即一块内存已经被释放了，而一个指针还在引用它），必须在最后的引用释放之后才删除对象。为满足这两条约束，采用一定的策略是很重要的。</p>
		<p>
				<a name="IDAQMYFC">为内存管理而管理对象所有权 <br />除了垃圾收集之外，通常还有其他两种方法用于处理别名问题： 引用计数和所有权管理。引用计数（reference counting）是对一个给定的对象当前有多少指向它的引用保留有一个计数，然后当最后一个引用被释放时自动删除该对象。在 C和20世纪90年代中期之前的多数 C++ 版本中，这是不可能自动完成的。标准模板库（Standard Template Library，STL）允许创建“灵巧”指针，而不能自动实现引用计数（要查看一些例子，请参见开放源代码 Boost 库中的 shared_ptr 类，或者参见STL中的更加简单的 auto_ptr 类）。 </a>
		</p>
		<p>所有权管理（ownership management） 是这样一个过程，该过程指明一个指针是“拥有”指针（"owning" pointer），而 所有其他别名只是临时的二类副本（ temporary second-class copies），并且只在所拥有的指针被释放时才删除对象。在有些情况下，所有权可以从一个指针“转移”到另一个指针，比如一个这样的方法，它以一个缓冲区作为参数，该方法用于向一个套接字写数据，并且在写操作完成时删除这个缓冲区。这样的方法通常叫做接收器 （sinks）。在这个例子中，缓冲区的所有权已经被有效地转移，因而进行调用的代码必须假设在被调用方法返回时缓冲区已经被删除。（通过确保所有的别名指针都具有与调用堆栈（比如方法参数或局部变量）一致的作用域（scope ），可以进一步简化所有权管理，如果引用将由非堆栈作用域的变量保存，则通过复制对象来进行简化。）</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp06243/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">那么，怎么着？</font>
								</strong>
						</span>
				</a>
		</p>
		<p>此时，您可能正纳闷，为什么我还要讨论内存管理、别名和对象所有权。毕竟，垃圾收集是 Java语言的核心特性之一，而内存管理是已经过时的一件麻烦事。就让垃圾收集器来处理这件事吧，这正是它的工作。那些从内存管理的麻烦中解脱出来的人不愿意再回到过去，而那些从未处理过内存管理的人则根本无法想象在过去倒霉的日子里――比如1996年――程序员的编程是多么可怕。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp06243/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">提防悬空别名</font>
								</strong>
						</span>
				</a>
		</p>
		<p>那么这意味着我们可以与对象所有权的概念说再见了吗？可以说是，也可以说不是。 大多数情况下，垃圾收集确实消除了显式资源存储单元分配（explicit resource deallocation）的必要（在以后的专栏中我将讨论一些例外）。但是，有一个区域中，所有权管理仍然是Java 程序中的一个问题，而这就是悬空别名（dangling aliases）问题。 Java 开发者通常依赖于这样一个隐含的假设，即假设由对象所有权来确定哪些引用应该被看作是只读的 （在C++中就是一个 const 指针），哪些引用可以用来修改被引用的对象的状态。当两个类都（错误地）认为自己保存有对给定对象的惟一可写的引用时，就会出现悬空指针。发生这种情况时，如果对象的状态被意外地更改，这两个类中的一个或两者将会产生混淆。 </p>
		<p>
				<a name="N10099">
						<span class="smalltitle">
								<strong>一个贴切的例子</strong>
						</span>
				</a>
		</p>
		<p>考虑清单 2 中的代码，其中的 UI 组件保存有一个 Point 对象，用于表示它的位置。当调用 MathUtil.calculateDistance 来计算对象移动了多远时，我们依赖于一个隐含而微妙的假设――即 calculateDistance 不会改变传递给它的 Point 对象的状态，或者情况更坏，维护着对那些 Point 对象的一个引用（比如通过将它们保存在集合中或者将它们传递到另一个线程），然后这个引用将用于在 calculateDistance 返回后更改Point 对象的状态。 在 calculateDistance的例子中，为这种行为担心似乎有些可笑，因为这明显是一个可怕的违背惯例的情况。但是，如果要说将一个可变的对象传递给一个方法，之后对象还能够毫发无损地返回来，并且将来对于对象的状态也不会有不可预料的副作用（比如该方法与另一个线程共享引用，该线程可能会等待5分钟，然后更改对象的状态），那么这只不过是一厢情愿的想法而已。 </p>
		<br />
		<a name="IDAGPYFC">
				<b>清单 2. 将可变对象传递给外部方法是不可取的</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    private Point initialLocation, currentLocation;

    public Widget(Point initialLocation) {
        this.initialLocation = initialLocation;
        this.currentLocation = initialLocation;
    }

    public double getDistanceMoved() {
        return MathUtil.calculateDistance(initialLocation, currentLocation);
    }
    
    . . . 

    // The ill-behaved utility class MathUtil
    public static double calculateDistance(Point p1, 
                                           Point p2) {
        double distance = Math.sqrt((p2.x - p1.x) ^ 2 
                                    + (p2.y - p1.y) ^ 2);
        p2.x = p1.x;
        p2.y = p1.y;
        return distance;
    }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="IDAQPYFC">
						<span class="smalltitle">
								<strong>一个愚蠢的例子</strong>
						</span>
				</a>
		</p>
		<p>大家对该例子明显而普遍的反应就是――这是一个愚蠢的例子――只是强调了这样一个事实，即对象所有权的概念在 Java 程序中依然存在，而且存在得很好，只是没有说明而已。calculateDistance 方法不应该改变它的参数的状态，因为它并不“拥有”它们――当然，调用方法拥有它们。因此说不用考虑对象所有权。 </p>
		<p>下面是一个更加实用的例子，它说明了不知道谁拥有对象就有可能会引起混淆。再次考虑一个以Point 属性 来表示其位置的 UI组件。 清单 3 显示了实现存取器方法 setLocation 和 getLocation的三种方式。第一种方式是最懒散的，并且提供了最好的性能，但是对于蓄意攻击和无意识的失误，它有几个薄弱环节。</p>
		<br />
		<a name="IDAOQYFC">
				<b>清单 3. getters 和 setters的值语义以及引用语义</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class Widget {
    private Point location;

    // Version 1: No copying -- getter and setter implement reference 
    // semantics
    // This approach effectively assumes that we are transferring 
    // ownership of the Point from the caller to the Widget, but this 
    // assumption is rarely explicitly documented. 
    public void setLocation(Point p) {
        this.location = p;
    }

    public Point getLocation() {
        return location;
    }

    // Version 2: Defensive copy on setter, implementing value 
    // semantics for the setter
    // This approach effectively assumes that callers of 
    // getLocation will respect the assumption that the Widget 
    // owns the Point, but this assumption is rarely documented.
    public void setLocation(Point p) {
        this.location = new Point(p.x, p.y);
    }

    public Point getLocation() {
        return location;
    }

    // Version 3: Defensive copy on getter and setter, implementing 
    // true value semantics, at a performance cost
    public void setLocation(Point p) {
        this.location = new Point(p.x, p.y);
    }

    public Point getLocation() {
        return (Point) location.clone();
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>现在来考虑 setLocation 看起来是无意的使用 ：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    Widget w1, w2;
    . . . 
    Point p = new Point();
    p.x = p.y = 1;
    w1.setLocation(p);
    
    p.x = p.y = 2;
    w2.setLocation(p);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>或者是：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    w2.setLocation(w1.getLocation());
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在setLocation/getLocation存取器实现的版本 1 之下，可能看起来好像第一个Widget的 位置是 (1, 1) ，第二个Widget的位置是 (2, 2)，而事实上，二者都是 (2, 2)。这可能对于调用者（因为第一个Widget意外地移动了）和Widget 类（因为它的位置改变了，而与Widget代码无关）来说都会产生混淆。在第二个例子中，您可能认为自己只是将Widget w2移动到 Widget w1当前所在的位置 ，但是实际上您这样做便规定了每次w1 移动时w2都跟随w1 。</p>
		<p>
				<a name="N100D3">
						<span class="smalltitle">
								<strong>防御性副本</strong>
						</span>
				</a>
		</p>
		<p>setLocation 的版本 2 做得更好：它创建了传递给它的参数的一个副本，以确保不存在可以意外改变其状态的 Point的别名。但是它也并非无可挑剔，因为下面的代码也将具有一个很可能不希望出现的效果，即Widget在不知情的情况下被移动了： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    Point p = w1.getLocation();
    . . .
    p.x = 0;
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>getLocation 和 setLocation 的版本 3 对于别名引用的恶意或无意使用是完全安全的。这一安全是以一些性能为代价换来的：每次调用一个 getter 或 setter 都会创建一个新对象。</p>
		<p>getLocation 和 setLocation 的不同版本具有不同的语义，通常这些语义被称作值语义（版本 1）和引用语义（版本 3）。不幸的是，通常没有说明实现者应该使用的是哪种语义。结果，这个类的使用者并不清楚这一点，从而作出了更差的假设（即选择了不是最合适的语义）。 </p>
		<p>getLocation 和 setLocation 的版本 3 所使用的技术叫做防御性复制（ defensive copying），尽管存在着明显的性能上的代价，您也应该养成这样的习惯，即几乎每次返回和存储对可变对象或数组的引用时都使用这一技术，尤其是在您编写一个通用的可能被不是您自己编写的代码调用（事实上这很常见）的工具时更是如此。有别名的可变对象被意外修改的情况会以许多微妙且令人惊奇的方式突然出现，并且调试起来相当困难。</p>
		<p>而且情况还会变得更坏。假设您是Widget类的一个使用者，您并不知道存取器具有值语义还是引用语义。 谨慎的做法是，在调用存取器方法时也使用防御性副本。所以，如果您想要将 w2 移动到 w1 的当前位置，您应该这样去做：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">    Point p = w1.getLocation();
    w2.setLocation(new Point(p.x, p.y));
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果 Widget 像在版本 2 或 3 中一样实现其存取器，那么我们将为每个调用创建两个临时对象 ――一个在 setLocation 调用的外面，一个在里面。 </p>
		<p>
				<a name="N100F3">
						<span class="smalltitle">
								<strong>文档说明存取器语义</strong>
						</span>
				</a>
		</p>
		<p>getLocation 和 setLocation 的版本 1 的真正问题不是它们易受混淆别名副作用的不良影响（确实是这样），而是它们的语义没有清楚的说明。如果存取器被清楚地说明为具有引用语义（而不是像通常那样被假设为值语义），那么调用者将更可能认识到，在它们调用setLocation时，它们是将Point对象的所有权转移给另一个实体，并且也不大可能仍然认为它们还拥有Point对象的所有权，因而还能够再次使用它。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp06243/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">利用不可改变性解决以上问题</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果一开始就使得Point 成为不可变的，那么这些与 Point 有关的问题早就迎刃而解了。不可变对象上没有副作用，并且缓存不可变对象的引用总是安全的，不会出现别名问题。如果 Point是不可变的，那么与setLocation 和 getLocation存取器的语义有关的所有问题都是非常确定的 。不可变属性的存取器将总是具有值引用，因而调用的任何一方都不需要防御性复制，这使得它们效率更高。</p>
		<p>那么为什么不在一开始就使得Point 成为不可变的呢？这可能是出于性能上的原因，因为早期的 JVM具有不太有效的垃圾收集器。 那时，每当一个对象（甚至是鼠标）在屏幕上移动就创建一个新的Point的对象创建开销可能有些让人生畏，而创建防御性副本的开销则不在话下。</p>
		<p>依后见之明，使Point成为可变的这个决定被证明对于程序清晰性和性能是昂贵的代价。Point类的可变性使得每一个接受Point作为参数或者要返回一个Point的方法背上了编写文档说明的沉重负担。也就是说，它得说明它是要改变Point，还是在返回之后保留对Point的一个引用。因为很少有类真正包含这样的文档，所以在调用一个没有用文档说明其调用语义或副作用行为的方法时，安全的策略是在传递它到任何这样的方法之前创建一份防御副本。</p>
		<p>有讽刺意味的是，使 Point成为可变的这个决定所带来的性能优势被由于Point的可变性而需要进行的防御性复制给抵消了。由于缺乏清晰的文档说明（或者缺少信任），在方法调用的两边都需要创建防御副本 ――调用者需要这样做是因为它不知道被调用者是否会粗暴地改变 Point，而被调用者需要这样做是因为它不知道是否保留了对 Point 的引用。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp06243/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">一个现实的例子</font>
								</strong>
						</span>
				</a>
		</p>
		<p>下面是悬空别名问题的另一个例子，该例子非常类似于我最近在一个服务器应用中所看到的。 该应用在内部使用了发布－订阅式消息传递方式，以将事件和状态更新传达到服务器内的其他代理。这些代理可以订阅任何一个它们感兴趣的消息流。一旦发布之后，传递到其他代理的消息就可能在将来某个时候在一个不同的线程中被处理。</p>
		<p>清单 4 显示了一个典型的消息传递事件（即发布拍卖系统中一个新的高投标通知）和产生该事件的代码。不幸的是，消息传递事件实现和调用者实现的交互合起来创建了一个悬空别名。通过简单地复制而不是克隆数组引用，消息和产生消息的类都保存了前一投标数组的主副本的一个引用。如果消息发布时的时间和消费时的时间有任何延迟，那么订阅者看到的 previous5Bids 数组的值将不同于消息发布时的时间，并且多个订阅者看到的前面投标的值可能会互不相同。在这个例子中，订阅者将看到当前投标的历史值和前面投标的更接近现在的值，从而形成了这样的错觉，认为前面投标比当前投标的值要高。不难设想这将如何引起问题――这还不算，当应用在很大的负载下时，这样一个问题则更是暴露无遗。 使得消息类不可变并在构造时克隆像数组这样的可变引用，就可以防止该问题。</p>
		<br />
		<a name="IDAWZYFC">
				<b>清单 4. 发布－订阅式消息传递代码中的悬空数组别名</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public interface MessagingEvent { ... }

public class CurrentBidEvent implements MessagingEvent { 
  public final int currentBid;
  public final int[] previous5Bids;

  public CurrentBidEvent(int currentBid, int[] previousBids) {
    this.currentBid = currentBid;
    // Danger -- copying array reference instead of values
    this.previous5Bids = previous5Bids;
  }

  ...
}

  // Now, somewhere in the bid-processing code, we create a 
  // CurrentBidEvent and publish it.  
  public void newBid(int newBid) { 
    if (newBid &gt; currentBid) { 
      for (int i=1; i&lt;5; i++) 
        previous5Bids[i] = previous5Bids[i-1];
      previous5Bids[0] = currentBid;
      currentBid = newBid;

      messagingTopic.publish(new CurrentBidEvent(currentBid, previousBids));
    }
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
												<br />
												<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp06243/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="6">
						<span class="atitle">
								<strong>
										<font size="4">可变对象的指导</font>
								</strong>
						</span>
				</a>
		</p>
		<p>如果您要创建一个可变类 M，那么您应该准备编写比 M 是不可变的情况下多得多的文档说明，以说明怎样处理 M 的引用。 首先，您必须选择以 M 为参数或返回 M 对象的方法是使用值语义还是引用语义，并准备在每一个在其接口内使用 M 的其他类中清晰地文档说明这一点 。如果接受或返回 M 对象的任何方法隐式地假设 M 的所有权被转移，那么您必须也文档说明这一点。您还要准备着接受在必要时创建防御副本的性能开销。</p>
		<p>一个必须处理对象所有权问题的特殊情况是数组，因为数组不可以是不可变的。当传递一个数组引用到另一个类时，可能有创建防御副本的代价，除非您能确保其他类要么创建了它自己的副本，要么只在调用期间保存引用，否则您可能需要在传递数组之前创建副本。另外，您可以容易地结束这样一种情形，即调用的两边的类都隐式地假设它们拥有数组，只是这样会有不可预知的结果出现。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65585.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:33 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65585.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>轻松使用线程: 不共享有时是最好的</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65584.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:32:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65584.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65584.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65584.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65584.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65584.html</trackback:ping><description><![CDATA[
		<blockquote>
				<code>
						<font face="Courier" size="2">ThreadLocal</font>
				</code> 类是悄悄地出现在 Java 平台版本 1.2 中的。虽然支持线程局部变量早就是许多线程工具（例如 Posix <code><font face="Courier" size="2">pthreads</font></code> 工具）的一部分，但 Java Threads API 的最初设计却没有这项有用的功能。而且，最初的实现也相当低效。由于这些原因， <code><font face="Courier" size="2">ThreadLocal</font></code> 极少受到关注，但对简化线程安全并发程序的开发来说，它却是很方便的。在 <i>轻松使用线程</i>的第 3 部分，Java 软件顾问 Brian Goetz 研究了 <code><font face="Courier" size="2">ThreadLocal</font></code> 并提供了一些使用技巧。 
<p>参加 Brian 的 <a href="http://www.ibm.com/developerworks/java_df.nsf/AllViewTemplate?OpenForm&amp;RestrictToCategory=23"><font color="#5c81a7">多线程 Java 编程讨论论坛</font></a>以获得您工程中的线程和并发问题的帮助。 </p></blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写，而且要求仔细分析其它类能如何使用某个类。 有时，要在不影响类的功能、易用性或性能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息，要在实践中使这样的类成为线程安全的是困难的。</p>
		<p>管理非线程安全类的使用比试图使类成为线程安全的要更容易些。非线程安全类通常可以安全地在多线程程序中使用，只要您能确保一个线程所用的类的实例不被其它线程使用。例如，JDBC <code><font face="Courier" size="2">Connection</font></code> 类是非线程安全的 — 两个线程不能在小粒度级上安全地共享一个 <code><font face="Courier" size="2">Connection</font></code> — 但如果每个线程都有它自己的 <code><font face="Courier" size="2">Connection</font></code> ，那么多个线程就可以同时安全地进行数据库操作。 </p>
		<p>不使用 <code><font face="Courier" size="2">ThreadLocal</font></code> 为每个线程维护一个单独的 JDBC 连接（或任何其它对象）当然是可能的；Thread API 给了我们把对象和线程联系起来所需的所有工具。而 ThreadLocal 则使我们能更容易地把线程和它的每线程（per-thread）数据成功地联系起来。 </p>
		<p>
				<a name="1">
						<span class="atitle">
								<strong>
										<font size="4">什么是线程局部变量（thread-local variable）？</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<i>线程局部变量</i>高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值，而不知道别的线程可能正在使用或修改它们自己的副本。一些编译器（例如 Microsoft Visual C++ 编译器或 IBM XL FORTRAN 编译器）用存储类别修饰符（像 <code><font face="Courier" size="2">static</font></code> 或 <code><font face="Courier" size="2">volatile</font></code> ）把对线程局部变量的支持集成到了其语言中。Java 编译器对线程局部变量不提供特别的语言支持；相反地，它用 <code><font face="Courier" size="2">ThreadLocal</font></code> 类实现这些支持， <code><font face="Courier" size="2">核心 Thread</font></code> 类中有这个类的特别支持。 </p>
		<p>因为线程局部变量是通过一个类来实现的，而不是作为 Java 语言本身的一部分，所以 Java 语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。要创建一个线程局部变量，请实例化类 <code><font face="Courier" size="2">ThreadLocal</font></code> 的一个对象。 <code><font face="Courier" size="2">ThreadLocal</font></code> 类的行为与 <code><font face="Courier" size="2">java.lang.ref</font></code> 中的各种 <code><font face="Courier" size="2">Reference</font></code> 类的行为很相似； <code><font face="Courier" size="2">ThreadLocal</font></code> 类充当存储或检索一个值时的间接句柄。清单 1 显示了 <code><font face="Courier" size="2">ThreadLocal</font></code> 接口。 </p>
		<br />
		<a name="listing1">
				<b>清单 1. ThreadLocal 接口</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class ThreadLocal { 
  public Object get();
  public void set(Object newValue);
  public Object initialValue();
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<code>
						<font face="Courier" size="2">get()</font>
				</code> 访问器检索变量的当前线程的值； <code><font face="Courier" size="2">set()</font></code> 访问器修改当前线程的值。 <code><font face="Courier" size="2">initialValue()</font></code> 方法是可选的，如果线程未使用过某个变量，那么您可以用这个方法来设置这个变量的初始值；它允许延迟初始化。用一个示例实现来说明 ThreadLocal 的工作方式是最好的方法。清单 2 显示了 <code><font face="Courier" size="2">ThreadLocal</font></code> 的一个实现方式。它不是一个特别好的实现（虽然它与最初实现非常相似），所以很可能性能不佳，但它清楚地说明了 ThreadLocal 的工作方式。 </p>
		<br />
		<a name="listing2">
				<b>清单 2. ThreadLocal 的糟糕实现</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class ThreadLocal { 
  private Map values = Collections.synchronizedMap(new HashMap());

  public Object get() {
    Thread curThread = Thread.currentThread();
    Object o = values.get(curThread);
    if (o == null &amp;&amp; !values.containsKey(curThread)) {
      o = initialValue();
      values.put(curThread, o);
    }
    return o;
  }

  public void set(Object newValue) {
    values.put(Thread.currentThread(), newValue);
  }

  public Object initialValue() {
    return null;
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这个实现的性能不会很好，因为每个 <code><font face="Courier" size="2">get()</font></code> 和 <code><font face="Courier" size="2">set()</font></code> 操作都需要 <code><font face="Courier" size="2">values</font></code> 映射表上的同步，而且如果多个线程同时访问同一个 <code><font face="Courier" size="2">ThreadLocal</font></code> ，那么将发生争用。此外，这个实现也是不切实际的，因为用 <code><font face="Courier" size="2">Thread</font></code> 对象做 <code><font face="Courier" size="2">values</font></code> 映射表中的关键字将导致无法在线程退出后对 <code><font face="Courier" size="2">Thread</font></code> 进行垃圾回收，而且也无法对死线程的 <code><font face="Courier" size="2">ThreadLocal</font></code> 的特定于线程的值进行垃圾回收。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/index3.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<strong>
										<font size="4">用 ThreadLocal 实现每线程 Singleton</font>
								</strong>
						</span>
				</a>
		</p>
		<p>线程局部变量常被用来描绘有状态“单子”（Singleton） 或线程安全的共享对象，或者是通过把不安全的整个变量封装进 <code><font face="Courier" size="2">ThreadLocal</font></code> ，或者是通过把对象的特定于线程的状态封装进 <code><font face="Courier" size="2">ThreadLocal</font></code> 。例如，在与数据库有紧密联系的应用程序中，程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个 <code><font face="Courier" size="2">Connection</font></code> 作为参数是不方便的 — 用“单子”来访问连接可能是一个虽然更粗糙，但却方便得多的技术。然而，多个线程不能安全地共享一个 JDBC <code><font face="Courier" size="2">Connection</font></code> 。如清单 3 所示，通过使用“单子”中的 <code><font face="Courier" size="2">ThreadLocal</font></code> ，我们就能让我们的程序中的任何类容易地获取每线程 <code><font face="Courier" size="2">Connection</font></code> 的一个引用。这样，我们可以认为 <code><font face="Courier" size="2">ThreadLocal</font></code> 允许我们创建 <i>每线程单子</i>。 </p>
		<br />
		<a name="listing3">
				<b>清单 3. 把一个 JDBC 连接存储到一个每线程 Singleton 中</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class ConnectionDispenser { 
  private static class ThreadLocalConnection extends ThreadLocal {
    public Object initialValue() {
      return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
    }
  }

  private ThreadLocalConnection conn = new ThreadLocalConnection();

  public static Connection getConnection() {
    return (Connection) conn.get();
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象，例如 JDBC <code><font face="Courier" size="2">Connection</font></code> 或正则表达式匹配器，都是可以使用每线程单子（singleton）技术的好地方。当然，在类似这样的地方，您可以使用其它技术，例如用池，来安全地管理共享访问。然而，从可伸缩性角度看，即使是用池也存在一些潜在缺陷。因为池实现必须使用同步，以维护池数据结构的完整性，如果所有线程使用同一个池，那么在有很多线程频繁地对池进行访问的系统中，程序性能将因争用而降低。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/index3.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<strong>
										<font size="4">用 ThreadLocal 简化调试日志纪录</font>
								</strong>
						</span>
				</a>
		</p>
		<p>其它适合使用 <code><font face="Courier" size="2">ThreadLocal</font></code> 但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。例如，假设您想创建一个用于管理多线程应用程序调试信息的工具。您可以用如清单 4 所示的 <code><font face="Courier" size="2">DebugLogger</font></code> 类作为线程局部容器来累积调试信息。在一个工作单元的开头，您清空容器，而当一个错误出现时，您查询该容器以检索这个工作单元迄今为止生成的所有调试信息。 </p>
		<br />
		<a name="listing4">
				<b>清单 4. 用 ThreadLocal 管理每线程调试日志</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class DebugLogger {
  private static class ThreadLocalList extends ThreadLocal {
    public Object initialValue() {
      return new ArrayList();
    }

    public List getList() { 
      return (List) super.get(); 
    }
  }

  private ThreadLocalList list = new ThreadLocalList();
  private static String[] stringArray = new String[0];

  public void clear() {
    list.getList().clear();
  }

  public void put(String text) {
    list.getList().add(text);
  }

  public String[] get() {
    return list.getList().toArray(stringArray);
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在您的代码中，您可以调用 <code><font face="Courier" size="2">DebugLogger.put()</font></code> 来保存您的程序正在做什么的信息，而且，稍后如果有必要（例如发生了一个错误），您能够容易地检索与某个特定线程相关的调试信息。 与简单地把所有信息转储到一个日志文件，然后努力找出哪个日志记录来自哪个线程（还要担心线程争用日志纪录对象）相比，这种技术简便得多，也有效得多。 </p>
		<p>
				<code>
						<font face="Courier" size="2">ThreadLocal</font>
				</code> 在基于 servlet 的应用程序或工作单元是一个整体请求的任何多线程应用程序服务器中也是很有用的，因为在处理请求的整个过程中将要用到单个线程。您可以通过前面讲述的每线程单子技术用 <code><font face="Courier" size="2">ThreadLocal</font></code> 变量来存储各种每请求（per-request）上下文信息。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/index3.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<strong>
										<font size="4">ThreadLocal 的线程安全性稍差的堂兄弟，InheritableThreadLocal</font>
								</strong>
						</span>
				</a>
		</p>
		<p>ThreadLocal 类有一个亲戚，InheritableThreadLocal，它以相似的方式工作，但适用于种类完全不同的应用程序。创建一个线程时如果保存了所有 <code><font face="Courier" size="2">InheritableThreadLocal</font></code> 对象的值，那么这些值也将自动传递给子线程。如果一个子线程调用 <code><font face="Courier" size="2">InheritableThreadLocal</font></code> 的 <code><font face="Courier" size="2">get()</font></code> ，那么它将与它的父线程看到同一个对象。为保护线程安全性，您应该只对不可变对象（一旦创建，其状态就永远不会被改变的对象）使用 <code><font face="Courier" size="2">InheritableThreadLocal</font></code> ，因为对象被多个线程共享。 <code><font face="Courier" size="2">InheritableThreadLocal</font></code> 很合适用于把数据从父线程传到子线程，例如用户标识（user id）或事务标识（transaction id），但不能是有状态对象，例如 JDBC <code><font face="Courier" size="2">Connection</font></code> 。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/index3.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<strong>
										<font size="4">ThreadLocal 的性能</font>
								</strong>
						</span>
				</a>
		</p>
		<p>虽然线程局部变量早已赫赫有名并被包括 Posix <code><font face="Courier" size="2">pthreads</font></code> 规范在内的很多线程框架支持，但最初的 Java 线程设计中却省略了它，只是在 Java 平台的版本 1.2 中才添加上去。在很多方面， <code><font face="Courier" size="2">ThreadLocal</font></code> 仍在发展之中；在版本 1.3 中它被重写，版本 1.4 中又重写了一次，两次都专门是为了性能问题。 </p>
		<p>在 JDK 1.2 中， <code><font face="Courier" size="2">ThreadLocal</font></code> 的实现方式与清单 2 中的方式非常相似，除了用同步 <code><font face="Courier" size="2">WeakHashMap</font></code> 代替 <code><font face="Courier" size="2">HashMap</font></code> 来存储 values 之外。（以一些额外的性能开销为代价，使用 WeakHashMap 解决了无法对 Thread 对象进行垃圾回收的问题。）不用说， <code><font face="Courier" size="2">ThreadLocal</font></code> 的性能是相当差的。 </p>
		<p>Java 平台版本 1.3 提供的 <code><font face="Courier" size="2">ThreadLocal</font></code> 版本已经尽量更好了；它不使用任何同步，从而不存在可伸缩性问题，而且它也不使用弱引用。相反地，人们通过给 <code><font face="Courier" size="2">Thread</font></code> 添加一个实例变量（该变量用于保存当前线程的从线程局部变量到它的值的映射的 <code><font face="Courier" size="2">HashMap</font></code> ）来修改 <code><font face="Courier" size="2">Thread</font></code> 类以支持 <code><font face="Courier" size="2">ThreadLocal</font></code> 。因为检索或设置一个线程局部变量的过程不涉及对可能被另一个线程读写的数据的读写操作，所以您可以不用任何同步就实现 <code><font face="Courier" size="2">ThreadLocal.get()</font></code> 和 <code><font face="Courier" size="2">set()</font></code> 。而且，因为每线程值的引用被存储在自已的 <code><font face="Courier" size="2">Thread</font></code> 对象中，所以当对 <code><font face="Courier" size="2">Thread</font></code> 进行垃圾回收时，也能对该 <code><font face="Courier" size="2">Thread</font></code> 的每线程值进行垃圾回收。 </p>
		<p>不幸的是，即使有了这些改进，Java 1.3 中的 <code><font face="Courier" size="2">ThreadLocal</font></code> 的性能仍然出奇地慢。据我的粗略测量，在双处理器 Linux 系统上的 Sun 1.3 JDK 中进行 <code><font face="Courier" size="2">ThreadLocal.get()</font></code> 操作，所耗费的时间大约是无争用同步的两倍。性能这么差的原因是 <code><font face="Courier" size="2">Thread.currentThread()</font></code> 方法的花费非常大，占了 <code><font face="Courier" size="2">ThreadLocal.get()</font></code> 运行时间的三分之二还多。虽然有这些缺点，JDK 1.3 <code><font face="Courier" size="2">ThreadLocal.get()</font></code> 仍然比争用同步快得多，所以如果在任何存在严重争用的地方（可能是有非常多的线程，或者同步块被频繁地执行，或者同步块很大）， <code><font face="Courier" size="2">ThreadLocal</font></code> 可能仍然要高效得多。 </p>
		<p>在 Java 平台的最新版本，即版本 1.4b2 中， <code><font face="Courier" size="2">ThreadLocal</font></code> 和 <code><font face="Courier" size="2">Thread.currentThread()</font></code> 的性能都有了很大提高。有了这些提高， <code><font face="Courier" size="2">ThreadLocal</font></code> 应该比其它技术，如用池，更快。由于它比其它技术更简单，也更不易出错，人们最终将发现它是避免线程间出现不希望的交互的有效途径。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-threads/index3.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="6">
						<span class="atitle">
								<strong>
										<font size="4">ThreadLocal 的好处</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<code>
						<font face="Courier" size="2">ThreadLocal</font>
				</code> 能带来很多好处。它常常是把有状态类描绘成线程安全的，或者封装非线程安全类以使它们能够在多线程环境中安全地使用的最容易的方式。使用 <code><font face="Courier" size="2">ThreadLocal</font></code> 使我们可以绕过为实现线程安全而对何时需要同步进行判断的复杂过程，而且因为它不需要任何同步，所以也改善了可伸缩性。除简单之外，用 <code><font face="Courier" size="2">ThreadLocal</font></code> 存储每线程单子或每线程上下文信息在归档方面还有一个颇有价值好处 — 通过使用 <code><font face="Courier" size="2">ThreadLocal</font></code> ，存储在 <code><font face="Courier" size="2">ThreadLocal</font></code> 中的对象都是 <i>不</i>被线程共享的是清晰的，从而简化了判断一个类是否线程安全的工作。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65584.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:32 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65584.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论和实践: 了解泛型</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65582.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:31:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65582.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65582.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65582.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65582.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65582.html</trackback:ping><description><![CDATA[
		<blockquote>JDK 5.0 中增加的泛型类型，是 Java 语言中类型安全的一次重要改进。但是，对于初次使用泛型类型的用户来说，泛型的某些方面看起来可能不容易明白，甚至非常奇怪。在本月的“<i>Java 理论和实践</i>”中，Brian Goetz 分析了束缚第一次使用泛型的用户的常见陷阱。您可以通过<a href="javascript:void forumWindow()"><font color="#996699">讨论论坛</font></a>与作者和其他读者分享您对本文的看法。（也可以单击本文顶端或底端的<b>讨论</b>来访问这个论坛。）</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>表面上看起来，无论语法还是应用的环境（比如容器类），泛型类型（或者泛型）都类似于 C++ 中的模板。但是这种相似性仅限于表面，Java 语言中的泛型基本上完全在编译器中实现，由编译器执行类型检查和类型推断，然后生成普通的非泛型的字节码。这种实现技术称为<i>擦除（erasure）</i>（编译器使用泛型类型信息保证类型安全，然后在生成字节码之前将其清除），这项技术有一些奇怪，并且有时会带来一些令人迷惑的后果。虽然范型是 Java 类走向类型安全的一大步，但是在学习使用泛型的过程中几乎肯定会遇到头痛（有时候让人无法忍受）的问题。 </p>
		<p>
				<i>
						<b>注意：</b>本文假设您对 JDK 5.0 中的范型有基本的了解。</i>
		</p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">泛型不是协变的</font>
								</strong>
						</span>
				</a>
		</p>
		<p>虽然将集合看作是数组的抽象会有所帮助，但是数组还有一些集合不具备的特殊性质。Java 语言中的数组是协变的（covariant），也就是说，如果 <code><font face="Courier" size="2">Integer</font></code> 扩展了 <code><font face="Courier" size="2">Number</font></code>（事实也是如此），那么不仅 <code><font face="Courier" size="2">Integer</font></code> 是 <code><font face="Courier" size="2">Number</font></code>，而且 <code><font face="Courier" size="2">Integer[]</font></code> 也是 <code><font face="Courier" size="2">Number[]</font></code>，在要求 <code><font face="Courier" size="2">Number[]</font></code> 的地方完全可以传递或者赋予 <code><font face="Courier" size="2">Integer[]</font></code>。（更正式地说，如果 <code><font face="Courier" size="2">Number</font></code> 是 <code><font face="Courier" size="2">Integer</font></code> 的超类型，那么 <code><font face="Courier" size="2">Number[]</font></code> 也是 <code><font face="Courier" size="2">Integer[]</font></code> 的超类型）。您也许认为这一原理同样适用于泛型类型 —— <code><font face="Courier" size="2">List&lt;Number&gt;</font></code> 是 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code> 的超类型，那么可以在需要 <code><font face="Courier" size="2">List&lt;Number&gt;</font></code> 的地方传递 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code>。不幸的是，情况并非如此。 </p>
		<p>不允许这样做有一个很充分的理由：这样做将破坏要提供的类型安全泛型。如果能够将 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code> 赋给 <code><font face="Courier" size="2">List&lt;Number&gt;</font></code>。那么下面的代码就允许将非 <code><font face="Courier" size="2">Integer</font></code> 的内容放入 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code>： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();
List&lt;Number&gt; ln = li; // illegal
ln.add(new Float(3.1415));
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>因为 <code><font face="Courier" size="2">ln</font></code> 是 <code><font face="Courier" size="2">List&lt;Number&gt;</font></code>，所以向其添加 <code><font face="Courier" size="2">Float</font></code> 似乎是完全合法的。但是如果 <code><font face="Courier" size="2">ln</font></code> 是 <code><font face="Courier" size="2">li</font></code> 的别名，那么这就破坏了蕴含在 <code><font face="Courier" size="2">li</font></code> 定义中的类型安全承诺 —— 它是一个整数列表，这就是泛型类型不能协变的原因。 </p>
		<p>
				<a name="1.1">
						<span class="smalltitle">
								<strong>其他的协变问题</strong>
						</span>
				</a>
		</p>
		<p>数组能够协变而泛型不能协变的另一个后果是，不能实例化泛型类型的数组（<code><font face="Courier" size="2">new List&lt;String&gt;[3]</font></code> 是不合法的），除非类型参数是一个未绑定的通配符（<code><font face="Courier" size="2">new List&lt;?&gt;[3]</font></code> 是合法的）。让我们看看如果允许声明泛型类型数组会造成什么后果： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">List&lt;String&gt;[] lsa = new List&lt;String&gt;[10]; // illegal
Object[] oa = lsa;  // OK because List&lt;String&gt; is a subtype of Object
List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();
li.add(new Integer(3));
oa[0] = li; 
String s = lsa[0].get(0); 
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>最后一行将抛出 <code><font face="Courier" size="2">ClassCastException</font></code>，因为这样将把 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code> 填入本应是 <code><font face="Courier" size="2">List&lt;String&gt;</font></code> 的位置。因为数组协变会破坏泛型的类型安全，所以不允许实例化泛型类型的数组（除非类型参数是未绑定的通配符，比如 <code><font face="Courier" size="2">List&lt;?&gt;</font></code>）。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp01255.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">构造延迟</font>
								</strong>
						</span>
				</a>
		</p>
		<p>因为可以擦除功能，所以 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code> 和 <code><font face="Courier" size="2">List&lt;String&gt;</font></code> 是同一个类，编译器在编译 <code><font face="Courier" size="2">List&lt;V&gt;</font></code> 时只生成一个类（和 C++ 不同）。因此，在编译 <code><font face="Courier" size="2">List&lt;V&gt;</font></code> 类时，编译器不知道 <code><font face="Courier" size="2">V</font></code> 所表示的类型，所以它就不能像知道类所表示的具体类型那样处理 <code><font face="Courier" size="2">List&lt;V&gt;</font></code> 类定义中的类型参数（<code><font face="Courier" size="2">List&lt;V&gt;</font></code> 中的 <code><font face="Courier" size="2">V</font></code>）。 </p>
		<p>因为运行时不能区分 <code><font face="Courier" size="2">List&lt;String&gt;</font></code> 和 <code><font face="Courier" size="2">List&lt;Integer&gt;</font></code>（运行时都是 <code><font face="Courier" size="2">List</font></code>），用泛型类型参数标识类型的变量的构造就成了问题。运行时缺乏类型信息，这给泛型容器类和希望创建保护性副本的泛型类提出了难题。 </p>
		<p>比如泛型类 <code><font face="Courier" size="2">Foo</font></code>： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class Foo&lt;T&gt; { 
  public void doSomething(T param) { ... }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>假设 <code><font face="Courier" size="2">doSomething()</font></code> 方法希望复制输入的 <code><font face="Courier" size="2">param</font></code> 参数，会怎么样呢？没有多少选择。您可能希望按以下方式实现 <code><font face="Courier" size="2">doSomething()</font></code>： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public void doSomething(T param) { 
  T copy = new T(param);  // illegal
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但是您不能使用类型参数访问构造函数，因为在编译的时候还不知道要构造什么类，因此也就不知道使用什么构造函数。使用泛型不能表达“<code><font face="Courier" size="2">T</font></code> 必须拥有一个拷贝构造函数（copy constructor）”（甚至一个无参数的构造函数）这类约束，因此不能使用泛型类型参数所表示的类的构造函数。 </p>
		<p>
				<code>
						<font face="Courier" size="2">clone()</font>
				</code> 怎么样呢？假设在 <code><font face="Courier" size="2">Foo</font></code> 的定义中，<code><font face="Courier" size="2">T</font></code> 扩展了 <code><font face="Courier" size="2">Cloneable</font></code>： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class Foo&lt;T extends Cloneable&gt; { 
  public void doSomething(T param) {
    T copy = (T) param.clone();  // illegal 
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>不幸的是，仍然不能调用 <code><font face="Courier" size="2">param.clone()</font></code>。为什么呢？因为 <code><font face="Courier" size="2">clone()</font></code> 在 <code><font face="Courier" size="2">Object</font></code> 中是保护访问的，调用 <code><font face="Courier" size="2">clone()</font></code> 必须通过将 <code><font face="Courier" size="2">clone()</font></code> 改写公共访问的类引用来完成。但是重新声明 <code><font face="Courier" size="2">clone()</font></code> 为 public 并不知道 <code><font face="Courier" size="2">T</font></code>，因此克隆也无济于事。 </p>
		<p>
				<a name="2.1">
						<span class="smalltitle">
								<strong>构造通配符引用</strong>
						</span>
				</a>
		</p>
		<p>因此，不能复制在编译时根本不知道是什么类的类型引用。那么使用通配符类型怎么样？假设要创建类型为 <code><font face="Courier" size="2">Set&lt;?&gt;</font></code> 的参数的保护性副本。您知道 <code><font face="Courier" size="2">Set</font></code> 有一个拷贝构造函数。而且别人可能曾经告诉过您，如果不知道要设置的内容的类型，最好使用 <code><font face="Courier" size="2">Set&lt;?&gt;</font></code> 代替原始类型的 <code><font face="Courier" size="2">Set</font></code>，因为这种方法引起的未检查类型转换警告更少。于是，可以试着这样写： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class Foo {
  public void doSomething(Set&lt;?&gt; set) {
    Set&lt;?&gt; copy = new HashSet&lt;?&gt;(set);  // illegal
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>不幸的是，您不能用通配符类型的参数调用泛型构造函数，即使知道存在这样的构造函数也不行。不过您可以这样做： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class Foo {
  public void doSomething(Set&lt;?&gt; set) {
    Set&lt;?&gt; copy = new HashSet&lt;Object&gt;(set);  
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这种构造不那么直观，但它是类型安全的，而且可以像 <code><font face="Courier" size="2">new HashSet&lt;?&gt;(set)</font></code> 那样工作。 </p>
		<p>
				<a name="2.2">
						<span class="smalltitle">
								<strong>构造数组</strong>
						</span>
				</a>
		</p>
		<p>如何实现 <code><font face="Courier" size="2">ArrayList&lt;V&gt;</font></code>？假设类 <code><font face="Courier" size="2">ArrayList</font></code> 管理一个 <code><font face="Courier" size="2">V</font></code> 数组，您可能希望用 <code><font face="Courier" size="2">ArrayList&lt;V&gt;</font></code> 的构造函数创建一个 <code><font face="Courier" size="2">V</font></code> 数组： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class ArrayList&lt;V&gt; {
  private V[] backingArray;
  public ArrayList() {
    backingArray = new V[DEFAULT_SIZE]; // illegal
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但是这段代码不能工作 —— 不能实例化用类型参数表示的类型数组。编译器不知道 <code><font face="Courier" size="2">V</font></code> 到底表示什么类型，因此不能实例化 <code><font face="Courier" size="2">V</font></code> 数组。 </p>
		<p>Collections 类通过一种别扭的方法绕过了这个问题，在 Collections 类编译时会产生类型未检查转换的警告。<code><font face="Courier" size="2">ArrayList</font></code> 具体实现的构造函数如下： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">class ArrayList&lt;V&gt; {
  private V[] backingArray;
  public ArrayList() {
    backingArray = (V[]) new Object[DEFAULT_SIZE]; 
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>为何这些代码在访问 <code><font face="Courier" size="2">backingArray</font></code> 时没有产生 <code><font face="Courier" size="2">ArrayStoreException</font></code> 呢？无论如何，都不能将 <code><font face="Courier" size="2">Object</font></code> 数组赋给 <code><font face="Courier" size="2">String</font></code> 数组。因为泛型是通过擦除实现的，<code><font face="Courier" size="2">backingArray</font></code> 的类型实际上就是 <code><font face="Courier" size="2">Object[]</font></code>，因为 <code><font face="Courier" size="2">Object</font></code> 代替了 <code><font face="Courier" size="2">V</font></code>。这意味着：实际上这个类期望 <code><font face="Courier" size="2">backingArray</font></code> 是一个 <code><font face="Courier" size="2">Object</font></code> 数组，但是编译器要进行额外的类型检查，以确保它包含 <code><font face="Courier" size="2">V</font></code> 类型的对象。所以这种方法很奏效，但是非常别扭，因此不值得效仿（甚至连泛型 Collections 框架的作者都这么说，请参阅<a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp01255.html#resources"><font color="#996699">参考资料</font></a>）。 </p>
		<p>还有一种方法就是声明 <code><font face="Courier" size="2">backingArray</font></code> 为 <code><font face="Courier" size="2">Object</font></code> 数组，并在使用它的各个地方强制将它转化为 <code><font face="Courier" size="2">V[]</font></code>。仍然会看到类型未检查转换警告（与上一种方法一样），但是它使一些未明确的假设更清楚了（比如 <code><font face="Courier" size="2">backingArray</font></code> 不应逃避 <code><font face="Courier" size="2">ArrayList</font></code> 的实现）。 </p>
		<p>
				<a name="2.3">
						<span class="smalltitle">
								<strong>其他方法</strong>
						</span>
				</a>
		</p>
		<p>最好的办法是向构造函数传递类文字（<code><font face="Courier" size="2">Foo.class</font></code>），这样，该实现就能在运行时知道 <code><font face="Courier" size="2">T</font></code> 的值。不采用这种方法的原因在于向后兼容性 —— 新的泛型集合类不能与 Collections 框架以前的版本兼容。 </p>
		<p>下面的代码中 <code><font face="Courier" size="2">ArrayList</font></code> 采用了以下方法： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class ArrayList&lt;V&gt; implements List&lt;V&gt; {
  private V[] backingArray;
  private Class&lt;V&gt; elementType;

  public ArrayList(Class&lt;V&gt; elementType) {
    this.elementType = elementType;
    backingArray = (V[]) Array.newInstance(elementType, DEFAULT_LENGTH);
  }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但是等一等！仍然有不妥的地方，调用 <code><font face="Courier" size="2">Array.newInstance()</font></code> 时会引起未经检查的类型转换。为什么呢？同样是由于向后兼容性。<code><font face="Courier" size="2">Array.newInstance()</font></code> 的签名是： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public static Object newInstance(Class&lt;?&gt; componentType, int length)
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>而不是类型安全的： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public static&lt;T&gt; T[] newInstance(Class&lt;T&gt; componentType, int length)
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>为何 <code><font face="Courier" size="2">Array</font></code> 用这种方式进行泛化呢？同样是为了保持向后兼容。要创建基本类型的数组，如 <code><font face="Courier" size="2">int[]</font></code>，可以使用适当的包装器类中的 <code><font face="Courier" size="2">TYPE</font></code> 字段调用 <code><font face="Courier" size="2">Array.newInstance()</font></code>（对于 <code><font face="Courier" size="2">int</font></code>，可以传递 <code><font face="Courier" size="2">Integer.TYPE</font></code> 作为类文字）。用 <code><font face="Courier" size="2">Class&lt;T&gt;</font></code> 参数而不是 <code><font face="Courier" size="2">Class&lt;?&gt;</font></code> 泛化 <code><font face="Courier" size="2">Array.newInstance()</font></code>，对于引用类型有更好的类型安全，但是就不能使用 <code><font face="Courier" size="2">Array.newInstance()</font></code> 创建基本类型数组的实例了。也许将来会为引用类型提供新的 <code><font face="Courier" size="2">newInstance()</font></code> 版本，这样就两者兼顾了。 </p>
		<p>在这里可以看到一种模式 —— 与泛型有关的很多问题或者折衷并非来自泛型本身，而是保持和已有代码兼容的要求带来的副作用。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp01255.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">泛化已有的类</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在转化现有的库类来使用泛型方面没有多少技巧，但与平常的情况相同，向后兼容性不会凭空而来。我已经讨论了两个例子，其中向后兼容性限制了类库的泛化。 </p>
		<p>另一种不同的泛化方法可能不存在向后兼容问题，这就是 <code><font face="Courier" size="2">Collections.toArray(Object[])</font></code>。传入 <code><font face="Courier" size="2">toArray()</font></code> 的数组有两个目的 —— 如果集合足够小，那么可以将其内容直接放在提供的数组中。否则，利用反射（reflection）创建相同类型的新数组来接受结果。如果从头开始重写 Collections 框架，那么很可能传递给 <code><font face="Courier" size="2">Collections.toArray()</font></code> 的参数不是一个数组，而是一个类文字： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">interface Collection&lt;E&gt; { 
  public T[] toArray(Class&lt;T super E&gt; elementClass);
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>因为 Collections 框架作为良好类设计的例子被广泛效仿，但是它的设计受到向后兼容性约束，所以这些地方值得您注意，不要盲目效仿。 </p>
		<p>首先，常常被混淆的泛型 Collections API 的一个重要方面是 <code><font face="Courier" size="2">containsAll()</font></code>、<code><font face="Courier" size="2">removeAll()</font></code> 和 <code><font face="Courier" size="2">retainAll()</font></code> 的签名。您可能认为 <code><font face="Courier" size="2">remove()</font></code> 和 <code><font face="Courier" size="2">removeAll()</font></code> 的签名应该是： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">interface Collection&lt;E&gt; { 
  public boolean remove(E e);  // not really
  public void removeAll(Collection&lt;? extends E&gt; c);  // not really
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>但实际上却是： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">interface Collection&lt;E&gt; { 
  public boolean remove(Object o);  
  public void removeAll(Collection&lt;?&gt; c);
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>为什么呢？答案同样是因为向后兼容性。<code><font face="Courier" size="2">x.remove(o)</font></code> 的接口表明“如果 <code><font face="Courier" size="2">o</font></code> 包含在 <code><font face="Courier" size="2">x</font></code> 中，则删除它，否则什么也不做。”如果 <code><font face="Courier" size="2">x</font></code> 是一个泛型集合，那么 <code><font face="Courier" size="2">o</font></code> 不一定与 <code><font face="Courier" size="2">x</font></code> 的类型参数兼容。如果 <code><font face="Courier" size="2">removeAll()</font></code> 被泛化为只有类型兼容时才能调用（<code><font face="Courier" size="2">Collection&lt;? extends E&gt;</font></code>），那么在泛化之前，合法的代码序列就会变得不合法，比如： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">// a collection of Integers
Collection c = new HashSet();
// a collection of Objects
Collection r = new HashSet();
c.removeAll(r);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果上述片段用直观的方法泛化（将 <code><font face="Courier" size="2">c</font></code> 设为 <code><font face="Courier" size="2">Collection&lt;Integer&gt;</font></code>，<code><font face="Courier" size="2">r</font></code> 设为 <code><font face="Courier" size="2">Collection&lt;Object&gt;</font></code>），如果 <code><font face="Courier" size="2">removeAll()</font></code> 的签名要求其参数为 <code><font face="Courier" size="2">Collection&lt;? extends E&gt;</font></code> 而不是 no-op，那么就无法编译上面的代码。泛型类库的一个主要目标就是不打破或者改变已有代码的语义，因此，必须用比从头重新设计泛型所使用类型约束更弱的类型约束来定义 <code><font face="Courier" size="2">remove()</font></code>、<code><font face="Courier" size="2">removeAll()</font></code>、<code><font face="Courier" size="2">retainAll()</font></code> 和 <code><font face="Courier" size="2">containsAll()</font></code>。 </p>
		<p>在泛型之前设计的类可能阻碍了“显然的”泛型化方法。这种情况下就要像上例这样进行折衷，但是如果从头设计新的泛型类，理解 Java 类库中的哪些东西是向后兼容的结果很有意义，这样可以避免不适当的模仿。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp01255.html#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">擦除的实现</font>
								</strong>
						</span>
				</a>
		</p>
		<p>因为泛型基本上都是在 Java 编译器中而不是运行库中实现的，所以在生成字节码的时候，差不多所有关于泛型类型的类型信息都被“擦掉”了。换句话说，编译器生成的代码与您手工编写的不用泛型、检查程序的类型安全后进行强制类型转换所得到的代码基本相同。与 C++ 不同，<code><font face="Courier" size="2">List&lt;Integer&gt;</font></code> 和 <code><font face="Courier" size="2">List&lt;String&gt;</font></code> 是同一个类（虽然是不同的类型但都是 <code><font face="Courier" size="2">List&lt;?&gt;</font></code> 的子类型，与以前的版本相比，在 JDK 5.0 中这是一个更重要的区别）。 </p>
		<p>擦除意味着一个类不能同时实现 <code><font face="Courier" size="2">Comparable&lt;String&gt;</font></code> 和 <code><font face="Courier" size="2">Comparable&lt;Number&gt;</font></code>，因为事实上两者都在同一个接口中，指定同一个 <code><font face="Courier" size="2">compareTo()</font></code> 方法。声明 <code><font face="Courier" size="2">DecimalString</font></code> 类以便与 <code><font face="Courier" size="2">String</font></code> 与 <code><font face="Courier" size="2">Number</font></code> 比较似乎是明智的，但对于 Java 编译器来说，这相当于对同一个方法进行了两次声明： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class DecimalString implements Comparable&lt;Number&gt;, Comparable&lt;String&gt; { ... } // nope
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>擦除的另一个后果是，对泛型类型参数是用强制类型转换或者 <code><font face="Courier" size="2">instanceof</font></code> 毫无意义。下面的代码完全不会改善代码的类型安全性： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public &lt;T&gt; T naiveCast(T t, Object o) { return (T) o; }
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>编译器仅仅发出一个类型未检查转换警告，因为它不知道这种转换是否安全。<code><font face="Courier" size="2">naiveCast()</font></code> 方法实际上根本不作任何转换，<code><font face="Courier" size="2">T</font></code> 直接被替换为 <code><font face="Courier" size="2">Object</font></code>，与期望的相反，传入的对象被强制转换为 <code><font face="Courier" size="2">Object</font></code>。 </p>
		<p>擦除也是造成上述构造问题的原因，即不能创建泛型类型的对象，因为编译器不知道要调用什么构造函数。如果泛型类需要构造用泛型类型参数来指定类型的对象，那么构造函数应该接受类文字（<code><font face="Courier" size="2">Foo.class</font></code>）并将它们保存起来，以便通过反射创建实例。 </p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65582.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:31 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65582.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 理论与实践: 非阻塞算法简介</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65583.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 09:31:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65583.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65583.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65583.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65583.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65583.html</trackback:ping><description><![CDATA[
		<blockquote>Java™ 5.0 第一次让使用 Java 语言开发非阻塞算法成为可能，<code><font face="Courier" size="2">java.util.concurrent</font></code> 包充分地利用了这个功能。非阻塞算法属于并发算法，它们可以安全地派生它们的线程，不通过锁定派生，而是通过低级的原子性的硬件原生形式 —— 例如<i>比较和交换</i>。非阻塞算法的设计与实现极为困难，但是它们能够提供更好的吞吐率，对生存问题（例如死锁和优先级反转）也能提供更好的防御。在这期的 <i>Java 理论与实践</i> 中，并发性大师 Brian Goetz 演示了几种比较简单的非阻塞算法的工作方式。</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>在不只一个线程访问一个互斥的变量时，所有线程都必须使用同步，否则就可能会发生一些非常糟糕的事情。Java 语言中主要的同步手段就是 <code><font face="Courier" size="2">synchronized</font></code> 关键字（也称为<i>内在锁</i>），它强制实行互斥，确保执行 <code><font face="Courier" size="2">synchronized</font></code> 块的线程的动作，能够被后来执行受相同锁保护的 <code><font face="Courier" size="2">synchronized</font></code> 块的其他线程看到。在使用得当的时候，内在锁可以让程序做到线程安全，但是在使用锁定保护短的代码路径，而且线程频繁地争用锁的时候，锁定可能成为相当繁重的操作。 </p>
		<p>在 <a href="http://www.ibm.com/developerworks/cn/java/j-jtp11234/"><font color="#5c81a7">“流行的原子”</font></a> 一文中，我们研究了<i>原子变量</i>，原子变量提供了原子性的读-写-修改操作，可以在不使用锁的情况下安全地更新共享变量。原子变量的内存语义与 volatile 变量类似，但是因为它们也可以被原子性地修改，所以可以把它们用作不使用锁的并发算法的基础。 </p>
		<p>
				<a name="1.0">
						<span class="atitle">
								<strong>
										<font size="4">非阻塞的计数器</font>
								</strong>
						</span>
				</a>
		</p>
		<p>清单 1 中的 <code><font face="Courier" size="2">Counter</font></code> 是线程安全的，但是使用锁的需求带来的性能成本困扰了一些开发人员。但是锁是必需的，因为虽然增加看起来是单一操作，但实际是三个独立操作的简化：检索值，给值加 1，再写回值。（在 <code><font face="Courier" size="2">getValue</font></code> 方法上也需要同步，以保证调用 <code><font face="Courier" size="2">getValue</font></code> 的线程看到的是最新的值。虽然许多开发人员勉强地使自己相信忽略锁定需求是可以接受的，但忽略锁定需求并不是好策略。） </p>
		<p>在多个线程同时请求同一个锁时，会有一个线程获胜并得到锁，而其他线程被阻塞。JVM 实现阻塞的方式通常是挂起阻塞的线程，过一会儿再重新调度它。由此造成的上下文切换相对于锁保护的少数几条指令来说，会造成相当大的延迟。 </p>
		<br />
		<a name="listing1">
				<b>清单 1. 使用同步的线程安全的计数器</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public final class Counter {
    private long value = 0;

    public synchronized long getValue() {
        return value;
    }

    public synchronized long increment() {
        return ++value;
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>清单 2 中的 <code><font face="Courier" size="2">NonblockingCounter</font></code> 显示了一种最简单的非阻塞算法：使用 <code><font face="Courier" size="2">AtomicInteger</font></code> 的 <code><font face="Courier" size="2">compareAndSet()</font></code> （CAS）方法的计数器。<code><font face="Courier" size="2">compareAndSet()</font></code> 方法规定 “将这个变量更新为新值，但是如果从我上次看到这个变量之后其他线程修改了它的值，那么更新就失败”（请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-jtp11234"><font color="#5c81a7">“流行的原子”</font></a> 获得关于原子变量以及 “比较和设置” 的更多解释。） </p>
		<br />
		<a name="listing2">
				<b>清单 2. 使用 CAS 的非阻塞算法</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class NonblockingCounter {
    private AtomicInteger value;

    public int getValue() {
        return value.get();
    }

    public int increment() {
        int v;
        do {
            v = value.get();
        while (!value.compareAndSet(v, v + 1));
        return v + 1;
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>原子变量类之所以被称为<i>原子的</i>，是因为它们提供了对数字和对象引用的细粒度的原子更新，但是在作为非阻塞算法的基本构造块的意义上，它们也是原子的。非阻塞算法作为科研的主题，已经有 20 多年了，但是直到 Java 5.0 出现，在 Java 语言中才成为可能。 </p>
		<p>现代的处理器提供了特殊的指令，可以自动更新共享数据，而且能够检测到其他线程的干扰，而 <code><font face="Courier" size="2">compareAndSet()</font></code> 就用这些代替了锁定。（如果要做的只是递增计数器，那么 <code><font face="Courier" size="2">AtomicInteger</font></code> 提供了进行递增的方法，但是这些方法基于 <code><font face="Courier" size="2">compareAndSet()</font></code>，例如 <code><font face="Courier" size="2">NonblockingCounter.increment()</font></code>）。 </p>
		<p>非阻塞版本相对于基于锁的版本有几个性能优势。首先，它用硬件的原生形态代替 JVM 的锁定代码路径，从而在更细的粒度层次上（独立的内存位置）进行同步，失败的线程也可以立即重试，而不会被挂起后重新调度。更细的粒度降低了争用的机会，不用重新调度就能重试的能力也降低了争用的成本。即使有少量失败的 CAS 操作，这种方法仍然会比由于锁争用造成的重新调度快得多。 </p>
		<p>
				<code>
						<font face="Courier" size="2">NonblockingCounter</font>
				</code> 这个示例可能简单了些，但是它演示了所有非阻塞算法的一个基本特征 —— 有些算法步骤的执行是要冒险的，因为知道如果 CAS 不成功可能不得不重做。非阻塞算法通常叫作<i>乐观算法</i>，因为它们继续操作的假设是不会有干扰。如果发现干扰，就会回退并重试。在计数器的示例中，冒险的步骤是递增 —— 它检索旧值并在旧值上加一，希望在计算更新期间值不会变化。如果它的希望落空，就会再次检索值，并重做递增计算。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2.0">
						<span class="atitle">
								<strong>
										<font size="4">非阻塞堆栈</font>
								</strong>
						</span>
				</a>
		</p>
		<p>非阻塞算法稍微复杂一些的示例是清单 3 中的 <code><font face="Courier" size="2">ConcurrentStack</font></code>。<code><font face="Courier" size="2">ConcurrentStack</font></code> 中的 <code><font face="Courier" size="2">push()</font></code> 和 <code><font face="Courier" size="2">pop()</font></code> 操作在结构上与 <code><font face="Courier" size="2">NonblockingCounter</font></code> 上相似，只是做的工作有些冒险，希望在 “提交” 工作的时候，底层假设没有失效。<code><font face="Courier" size="2">push()</font></code> 方法观察当前最顶的节点，构建一个新节点放在堆栈上，然后，如果最顶端的节点在初始观察之后没有变化，那么就安装新节点。如果 CAS 失败，意味着另一个线程已经修改了堆栈，那么过程就会重新开始。 </p>
		<br />
		<a name="listing3">
				<b>清单 3. 使用 Treiber 算法的非阻塞堆栈</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class ConcurrentStack&lt;E&gt; {
    AtomicReference&lt;Node&lt;E&gt;&gt; head = new AtomicReference&lt;Node&lt;E&gt;&gt;();

    public void push(E item) {
        Node&lt;E&gt; newHead = new Node&lt;E&gt;(item);
        Node&lt;E&gt; oldHead;
        do {
            oldHead = head.get();
            newHead.next = oldHead;
        } while (!head.compareAndSet(oldHead, newHead));
    }

    public E pop() {
        Node&lt;E&gt; oldHead;
        Node&lt;E&gt; newHead;
        do {
            oldHead = head.get();
            if (oldHead == null) 
                return null;
            newHead = oldHead.next;
        } while (!head.compareAndSet(oldHead,newHead));
        return oldHead.item;
    }

    static class Node&lt;E&gt; {
        final E item;
        Node&lt;E&gt; next;

        public Node(E item) { this.item = item; }
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="2.1">
						<span class="smalltitle">
								<strong>性能考虑</strong>
						</span>
				</a>
		</p>
		<p>在轻度到中度的争用情况下，非阻塞算法的性能会超越阻塞算法，因为 CAS 的多数时间都在第一次尝试时就成功，而发生争用时的开销也不涉及线程挂起和上下文切换，只多了几个循环迭代。没有争用的 CAS 要比没有争用的锁便宜得多（这句话肯定是真的，因为没有争用的锁涉及 CAS 加上额外的处理），而争用的 CAS 比争用的锁获取涉及更短的延迟。 </p>
		<p>在高度争用的情况下（即有多个线程不断争用一个内存位置的时候），基于锁的算法开始提供比非阻塞算法更好的吞吐率，因为当线程阻塞时，它就会停止争用，耐心地等候轮到自己，从而避免了进一步争用。但是，这么高的争用程度并不常见，因为多数时候，线程会把线程本地的计算与争用共享数据的操作分开，从而给其他线程使用共享数据的机会。（这么高的争用程度也表明需要重新检查算法，朝着更少共享数据的方向努力。）<a href="http://www.ibm.com/developerworks/cn/java/j-jtp11234/"><font color="#5c81a7">“流行的原子”</font></a> 中的图在这方面就有点儿让人困惑，因为被测量的程序中发生的争用极其密集，看起来即使对数量很少的线程，锁定也是更好的解决方案。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3.0">
						<span class="atitle">
								<strong>
										<font size="4">非阻塞的链表</font>
								</strong>
						</span>
				</a>
		</p>
		<p>目前为止的示例（计数器和堆栈）都是非常简单的非阻塞算法，一旦掌握了在循环中使用 CAS，就可以容易地模仿它们。对于更复杂的数据结构，非阻塞算法要比这些简单示例复杂得多，因为修改链表、树或哈希表可能涉及对多个指针的更新。CAS 支持对单一指针的原子性条件更新，但是不支持两个以上的指针。所以，要构建一个非阻塞的链表、树或哈希表，需要找到一种方式，可以用 CAS 更新多个指针，同时不会让数据结构处于不一致的状态。 </p>
		<p>在链表的尾部插入元素，通常涉及对两个指针的更新：“尾” 指针总是指向列表中的最后一个元素，“下一个” 指针从过去的最后一个元素指向新插入的元素。因为需要更新两个指针，所以需要两个 CAS。在独立的 CAS 中更新两个指针带来了两个需要考虑的潜在问题：如果第一个 CAS 成功，而第二个 CAS 失败，会发生什么？如果其他线程在第一个和第二个 CAS 之间企图访问链表，会发生什么？ </p>
		<p>对于非复杂数据结构，构建非阻塞算法的 “技巧” 是确保数据结构总处于一致的状态（甚至包括在线程开始修改数据结构和它完成修改之间），还要确保其他线程不仅能够判断出第一个线程已经完成了更新还是处在更新的中途，还能够判断出如果第一个线程走向 AWOL，完成更新还需要什么操作。如果线程发现了处在更新中途的数据结构，它就可以 “帮助” 正在执行更新的线程完成更新，然后再进行自己的操作。当第一个线程回来试图完成自己的更新时，会发现不再需要了，返回即可，因为 CAS 会检测到帮助线程的干预（在这种情况下，是建设性的干预）。 </p>
		<p>这种 “帮助邻居” 的要求，对于让数据结构免受单个线程失败的影响，是必需的。如果线程发现数据结构正处在被其他线程更新的中途，然后就等候其他线程完成更新，那么如果其他线程在操作中途失败，这个线程就可能永远等候下去。即使不出现故障，这种方式也会提供糟糕的性能，因为新到达的线程必须放弃处理器，导致上下文切换，或者等到自己的时间片过期（而这更糟）。 </p>
		<p>清单 4 的 <code><font face="Courier" size="2">LinkedQueue</font></code> 显示了 Michael-Scott 非阻塞队列算法的插入操作，它是由 <code><font face="Courier" size="2">ConcurrentLinkedQueue</font></code> 实现的： </p>
		<br />
		<a name="listing4">
				<b>清单 4. Michael-Scott 非阻塞队列算法中的插入</b>
		</a>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public class LinkedQueue &lt;E&gt; {
    private static class Node &lt;E&gt; {
        final E item;
        final AtomicReference&lt;Node&lt;E&gt;&gt; next;

        Node(E item, Node&lt;E&gt; next) {
            this.item = item;
            this.next = new AtomicReference&lt;Node&lt;E&gt;&gt;(next);
        }
    }

    private AtomicReference&lt;Node&lt;E&gt;&gt; head
        = new AtomicReference&lt;Node&lt;E&gt;&gt;(new Node&lt;E&gt;(null, null));
    private AtomicReference&lt;Node&lt;E&gt;&gt; tail = head;

    public boolean put(E item) {
        Node&lt;E&gt; newNode = new Node&lt;E&gt;(item, null);
        while (true) {
            Node&lt;E&gt; curTail = tail.get();
            Node&lt;E&gt; residue = curTail.next.get();
            if (curTail == tail.get()) {
                if (residue == null) /* A */ {
                    if (curTail.next.compareAndSet(null, newNode)) /* C */ {
                        tail.compareAndSet(curTail, newNode) /* D */ ;
                        return true;
                    }
                } else {
                    tail.compareAndSet(curTail, residue) /* B */;
                }
            }
        }
    }
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>像许多队列算法一样，空队列只包含一个假节点。头指针总是指向假节点；尾指针总指向最后一个节点或倒数第二个节点。图 1 演示了正常情况下有两个元素的队列： </p>
		<br />
		<a name="fig1">
				<b>图 1. 有两个元素，处在静止状态的队列</b>
		</a>
		<br />
		<img height="118" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/quiescent.gif" width="349" />
		<br />
		<p>如 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#listing4"><font color="#996699">清单 4</font></a> 所示，插入一个元素涉及两个指针更新，这两个更新都是通过 CAS 进行的：从队列当前的最后节点（C）链接到新节点，并把尾指针移动到新的最后一个节点（D）。如果第一步失败，那么队列的状态不变，插入线程会继续重试，直到成功。一旦操作成功，插入被当成生效，其他线程就可以看到修改。还需要把尾指针移动到新节点的位置上，但是这项工作可以看成是 “清理工作”，因为任何处在这种情况下的线程都可以判断出是否需要这种清理，也知道如何进行清理。 </p>
		<p>队列总是处于两种状态之一：正常状态（或称静止状态，<a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#fig1"><font color="#996699">图 1</font></a> 和 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#fig3"><font color="#996699">图 3</font></a>）或中间状态（图 2）。在插入操作之前和第二个 CAS（D）成功之后，队列处在静止状态；在第一个 CAS（C）成功之后，队列处在中间状态。在静止状态时，尾指针指向的链接节点的 next 字段总为 null，而在中间状态时，这个字段为非 null。任何线程通过比较 <code><font face="Courier" size="2">tail.next</font></code> 是否为 null，就可以判断出队列的状态，这是让线程可以帮助其他线程 “完成” 操作的关键。 </p>
		<br />
		<a name="fig2">
				<b>图 2. 处在插入中间状态的队列，在新元素插入之后，尾指针更新之前</b>
		</a>
		<br />
		<img height="118" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/intermediate.gif" width="443" />
		<br />
		<p>插入操作在插入新元素（A）之前，先检查队列是否处在中间状态，如 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#listing4"><font color="#996699">清单 4</font></a> 所示。如果是在中间状态，那么肯定有其他线程已经处在元素插入的中途，在步骤（C）和（D）之间。不必等候其他线程完成，当前线程就可以 “帮助” 它完成操作，把尾指针向前移动（B）。如果有必要，它还会继续检查尾指针并向前移动指针，直到队列处于静止状态，这时它就可以开始自己的插入了。 </p>
		<p>第一个 CAS（C）可能因为两个线程竞争访问队列当前的最后一个元素而失败；在这种情况下，没有发生修改，失去 CAS 的线程会重新装入尾指针并再次尝试。如果第二个 CAS（D）失败，插入线程不需要重试 —— 因为其他线程已经在步骤（B）中替它完成了这个操作！ </p>
		<br />
		<a name="fig3">
				<b>图 3. 在尾指针更新后，队列重新处在静止状态</b>
		</a>
		<br />
		<img height="118" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/final.gif" width="444" />
		<br />
		<p>
				<a name="3.1">
						<span class="smalltitle">
								<strong>幕后的非阻塞算法</strong>
						</span>
				</a>
		</p>
		<p>如果深入 JVM 和操作系统，会发现非阻塞算法无处不在。垃圾收集器使用非阻塞算法加快并发和平行的垃圾搜集；调度器使用非阻塞算法有效地调度线程和进程，实现内在锁。在 Mustang（Java 6.0）中，基于锁的 <code><font face="Courier" size="2">SynchronousQueue</font></code> 算法被新的非阻塞版本代替。很少有开发人员会直接使用 <code><font face="Courier" size="2">SynchronousQueue</font></code>，但是通过 <code><font face="Courier" size="2">Executors.newCachedThreadPool()</font></code> 工厂构建的线程池用它作为工作队列。比较缓存线程池性能的对比测试显示，新的非阻塞同步队列实现提供了几乎是当前实现 3 倍的速度。在 Mustang 的后续版本（代码名称为 Dolphin）中，已经规划了进一步的改进。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-jtp04186/#main">
																				<b>
																						<font face="Verdana" color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4.0">
						<span class="atitle">
								<strong>
										<font size="4">结束语</font>
								</strong>
						</span>
				</a>
		</p>
		<p>非阻塞算法要比基于锁的算法复杂得多。开发非阻塞算法是相当专业的训练，而且要证明算法的正确也极为困难。但是在 Java 版本之间并发性能上的众多改进来自对非阻塞算法的采用，而且随着并发性能变得越来越重要，可以预见在 Java 平台的未来发行版中，会使用更多的非阻塞算法。</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65583.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 17:31 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65583.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个线程池的实现</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65562.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 08:55:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65562.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65562.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65562.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65562.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65562.html</trackback:ping><description><![CDATA[设计目标<br />Ø    提供一个线程池的组件，具有良好的伸缩性，当线程够用时，销毁不用线程，当线程不够用时，自动增加线程数量；<br />Ø    提供一个工作任务接口和工作队列，实际所需要的任务都必须实现这个工作任务接口，然后放入工作队列中；<br />Ø    线程池中的线程从工作队列中，自动取得工作任务，执行任务。<br />主要控制类和功能接口设计<br />线程池管理器 ThreadPoolManager 的功能：<br />Ø    管理线程池中的各个属性变量<br />ü    最大工作线程数<br />ü    最小工作线程数<br />ü    激活的工作线程总数<br />ü    睡眠的工作线程总数<br />ü    工作线程总数 (即：激活的工作线程总数+睡眠的工作线程总数)<br />Ø    创建工作线程<br />Ø    销毁工作线程<br />Ø    启动处于睡眠的工作线程<br />Ø    睡眠处于激活的工作线程<br />Ø    缩任务：当工作线程总数小于或等于最小工作线程数时，销毁多余的睡眠的工作线程，使得现有工作线程总数等于最小工作任务总数<br />Ø    伸任务：当任务队列任务总数大于工作线程数时，增加工作线程总数至最大工作线程数<br />Ø    提供线程池启动接口<br />Ø    提供线程池销毁接口<br />工作线程 WorkThread  的功能：<br />Ø    从工作队列取得工作任务<br />Ø    执行工作任务接口中的指定任务<br />工作任务接口 ITask   的功能：<br />Ø    提供指定任务动作<br />工作队列 IWorkQueue  的功能：<br />Ø    提供获取任务接口，并删除工作队列中的任务；<br />Ø    提供加入任务接口；<br />Ø    提供删除任务接口；<br />Ø    提供取得任务总数接口；<br />Ø    提供自动填任务接口；（当任务总数少于或等于默认总数的25％时，自动装填）<br />Ø    提供删除所有任务接口；<br /><br /><br />Code<br /><br /><br />ThreadPoolManager：<br />=====================================<br /><blockquote><font size="-1">CODE:</font><hr /><pre>package test.thread.pool1;
import java.util.ArrayList;
import java.util.List;
import test.thread.pool1.impl.MyWorkQueue;

/**
 * &lt;p&gt;Title: 线程池管理器&lt;/p&gt;
 * &lt;p&gt;Description: &lt;/p&gt;
 * &lt;p&gt;Copyright: Copyright (c) 2005&lt;/p&gt;
 * &lt;p&gt;Company: &lt;/p&gt;
 * @author not attributable
 * @version 1.0
 */

public class ThreadPoolManager {
  /*最大线程数*/
  private int threads_max_num;

  /*最小线程数*/
  private int threads_min_num;
  
  /* 线程池线程增长步长 */
  private int threads_increase_step = 5;

  /* 任务工作队列 */
  private IWorkQueue queue;
  
  /* 线程池监视狗 */
  private PoolWatchDog poolWatchDog ;
  
  /* 队列线程 */
  private Thread queueThread ;
  
  /* 线程池 封装所有工作线程的数据结构 */
  private List pool = new ArrayList();
  
  /* 线程池中 封装所有钝化后的数据结构*/
  private List passivePool = new ArrayList();
  
  /* 空闲60秒 */
  private static final long IDLE_TIMEOUT = 60000L;
  
  /* 关闭连接池标志位 */
  private boolean close = false;
  
  /**
   * 线程池管理器
   * @param queue 任务队列
   * @param threads_min_num 工作线程最小数
   * @param threads_max_num 工作线程最大数
   */
  public ThreadPoolManager(int threads_max_num
                          ,int threads_min_num
                          ,IWorkQueue queue){
    this.threads_max_num = threads_max_num;
    this.threads_min_num = threads_min_num;
    this.queue = queue;    
  }

  /**
   * 线程池启动
   */
  public void startPool(){
    System.out.println("=== startPool..........");
    poolWatchDog = new PoolWatchDog("PoolWatchDog");
    poolWatchDog.setDaemon(true);
    poolWatchDog.start();
    System.out.println("=== startPool..........over");
  }

  /**
   * 线程池销毁接口
   */
  public void destoryPool(){
    System.out.println("==========================DestoryPool starting ...");
    this.close = true;
    int pool_size = this.pool.size();
    
    //中断队列线程
    System.out.println("===Interrupt queue thread ... ");
    queueThread.interrupt();
    queueThread = null;
    
    System.out.println("===Interrupt thread pool ... ");
    Thread pool_thread = null;
    for(int i=0; i&lt;pool_size; i++){
      pool_thread = (Thread)pool.get(i);
      if(pool_thread !=null 
      &amp;&amp; pool_thread.isAlive() 
      &amp;&amp; !pool_thread.isInterrupted()){
        pool_thread.interrupt();
        System.out.println("Stop pool_thread:"
                          +pool_thread.getName()+"[interrupt] "
                          +pool_thread.isInterrupted());
      }
    }//end for
    
    if(pool != null){
      pool.clear();
    }
    if(passivePool != null){
      pool.clear();
    }
    
    try{
      System.out.println("=== poolWatchDog.join() starting ...");
      poolWatchDog.join();
      System.out.println("=== poolWatchDog.join() is over ...");
    }
    catch(Throwable ex){
      System.out.println("###poolWatchDog ... join method throw a exception ... "
                          +ex.toString());
    }
    
    poolWatchDog =null;
    System.out.println("==============================DestoryPool is over ...");    
  }
  
  
  public static void main(String[] args) throws Exception{
    ThreadPoolManager threadPoolManager1 = new ThreadPoolManager(10,5,new MyWorkQueue(50,30000));
    
    threadPoolManager1.startPool();
    Thread.sleep(60000);
    threadPoolManager1.destoryPool();
  }
  
  /**
   * 线程池监视狗
   */
  private class PoolWatchDog extends Thread{
    public PoolWatchDog(String name){
      super(name);
    }
  
    public void run(){
      Thread workThread = null;
      Runnable run = null;
      
      //开启任务队列线程，获取数据--------
      System.out.println("===QueueThread starting ... ... ");
      queueThread = new Thread(new QueueThread(),"QueueThread");
      queueThread.start();
      
      System.out.println("===Initial thread Pool ... ...");
      //初始化线程池的最小线程数,并放入池中
      for(int i=0; i&lt;threads_min_num; i++){
        run = new WorkThread();
        workThread = new Thread(run,"WorkThread_"+System.currentTimeMillis()+i);
        workThread.start();
        if(i == threads_min_num -1){
          workThread = null;
          run = null;
        }
      }
      System.out.println("===Initial thread Pool..........over ,and get pool's size:"+pool.size());

      //线程池线程动态增加线程算法--------------
      while(!close){
      
        //等待5秒钟，等上述线程都启动----------
        synchronized(this){          
          try{
            System.out.println("===Wait the [last time] threads starting ....");
            this.wait(15000);
          }
          catch(Throwable ex){
            System.out.println("###PoolWatchDog invoking is failure ... "+ex);
          }
        }//end synchronized
          
        //开始增加线程-----------------------spread动作
        int queue_size = queue.getTaskSize();
        int temp_size = (queue_size - threads_min_num);
        
        if((temp_size &gt; 0) &amp;&amp; (temp_size/threads_increase_step &gt; 2) ){
          System.out.println("================Spread thread pool starting ....");
          for(int i=0; i&lt;threads_increase_step &amp;&amp; (pool.size() &lt; threads_max_num); i++){
            System.out.println("=== Spread thread num : "+i);
            run = new WorkThread();
            workThread = new Thread(run,"WorkThread_"+System.currentTimeMillis()+i);
            workThread.start();
          }//end for
          
          workThread = null;
          run = null;    
          System.out.println("===Spread thread pool is over .... and pool size:"+pool.size());
        }//end if
          
        //删除已经多余的睡眠线程-------------shrink动作
        int more_sleep_size = pool.size() - threads_min_num;//最多能删除的线程数
        int sleep_threads_size = passivePool.size();
        if(more_sleep_size &gt;0 &amp;&amp; sleep_threads_size &gt;0){
          System.out.println("================Shrink thread pool starting ....");        
          for(int i=0; i &lt; more_sleep_size &amp;&amp; i &lt; sleep_threads_size ; i++){
            System.out.println("=== Shrink thread num : "+i);
            Thread removeThread = (Thread)passivePool.get(0);
            if(removeThread != null &amp;&amp; removeThread.isAlive() &amp;&amp; !removeThread.isInterrupted()){
              removeThread.interrupt();
            }
          }
          System.out.println("===Shrink thread pool is over .... and pool size:"+pool.size());          
        }

        System.out.println("===End one return [shrink - spread operator] ....");    
      }//end while
    }//end run 
  }//end private class
  
  /**
   * 工作线程
   */
  class WorkThread implements Runnable{
  
    public WorkThread(){
    }
  
    public void run(){
      String name = Thread.currentThread().getName();
      System.out.println("===Thread.currentThread():"+name);
      pool.add(Thread.currentThread());    
    
      while(true){
      
        //获取任务---------
        ITask task = null;
        try{
          System.out.println("===Get task from queue is starting ... ");
          //看线程是否被中断，如果被中断停止执行任务----
          if(Thread.currentThread().isInterrupted()){
            System.out.println("===Breaking current thread and jump whlie [1] ... ");
            break;
          }
          task = queue.getTask();
        }
        catch(Throwable ex){
          System.out.println("###No task in queue:"+ex);
        }//end tryc
        
        if(task != null){
          //执行任务---------
          try{
            System.out.println("===Execute the task is starting ... ");
            //看线程是否被中断，如果被中断停止执行任务----
            if(Thread.currentThread().isInterrupted()){
              System.out.println("===Breaking current thread and jump whlie [1] ... ");
              break;
            }     
            task.executeTask();
            //任务执行完毕-------
            System.out.println("===Execute the task is over ... ");
          }
          catch(Throwable ex){
            System.out.println("###Execute the task is failure ... "+ex);
          }//end tryc
          
        }else{
          //没有任务，则钝化线程至规定时间--------
          synchronized(this){
            try{
              System.out.println("===Passivate into passivePool ... ");
              
              //看线程是否被中断，如果被中断停止执行任务----
              boolean isInterrupted = Thread.currentThread().isInterrupted();
              if(isInterrupted){
                System.out.println("===Breaking current thread and jump whlie [1] ... ");
                break;
              }
//              passivePool.add(this);
            passivePool.add(Thread.currentThread());

              
              //准备睡眠线程-------
              isInterrupted = Thread.currentThread().isInterrupted();
              if(isInterrupted){
                System.out.println("===Breaking current thread and jump whlie [2] ... ");
                break;
              }              
              this.wait(IDLE_TIMEOUT);
            }
            catch(Throwable ex1){
              System.out.println("###Current Thread passivate is failure ... break while cycle. "+ex1);
              break;
            }
          }          
        }        
      }//end while--------
      
      if(pool.contains(passivePool)){
        pool.remove(this);
      }
      if(passivePool.contains(passivePool)){
        passivePool.remove(this);
      }
      System.out.println("===The thread execute over ... "); 
    }//end run----------
  }
  
  
  class QueueThread implements Runnable{
  
    public QueueThread(){
    }
  
    public void run(){
      while(true){
        //自动装在任务--------
        queue.autoAddTask();
        System.out.println("===The size of queue's task is "+queue.getTaskSize());
      
        synchronized(this){
          if(Thread.currentThread().isInterrupted()){
            break;
          }else{
              try{
                this.wait(queue.getLoadDataPollingTime());
              }
              catch(Throwable ex){
                System.out.println("===QueueThread invoked wait is failure ... break while cycle."+ex);
                break;
              }
          }//end if
        }//end synchr
        
      }//end while
    }//end run
  } 
}
</pre><hr /></blockquote><br /><br /><br /><br /><br />WorkQueue<br />=====================================<br /><blockquote><font size="-1">CODE:</font><hr /><pre>package test.thread.pool1;

import java.util.LinkedList;
import test.thread.pool1.impl.MyTask;

/**
 * &lt;p&gt;Title: 工作队列对象 &lt;/p&gt;
 * &lt;p&gt;Description: &lt;/p&gt;
 * &lt;p&gt;Copyright: Copyright (c) 2005&lt;/p&gt;
 * &lt;p&gt;Company: &lt;/p&gt;
 * @author not attributable
 * @version 1.0
 */

public abstract class WorkQueue implements IWorkQueue{
  /* 预计装载量 */
  private int load_size;
  
  /* 数据装载轮循时间 */
  private long load_polling_time;
  
  /* 队列 */
  private LinkedList queue = new LinkedList();
  
  /**
   * 
   * @param load_size 预计装载量
   * @param load_polling_time 数据装载轮循时间
   */
  public WorkQueue(int load_size,long load_polling_time){
    this.load_size = (load_size &lt;= 10) ? 10 : load_size;
    this.load_polling_time = load_polling_time;
  }

  /* 数据装载轮循时间 */
  public long getLoadDataPollingTime(){
    return this.load_polling_time;
  }


  /*获取任务，并删除队列中的任务*/
  public synchronized ITask getTask(){
    ITask task = (ITask)queue.getFirst();
    queue.removeFirst();
    return task;
  }

  /*加入任务*/
  public void  addTask(ITask task){
    queue.addLast(task);
  }

  /*删除任务*/
  public synchronized void removeTask(ITask task){
    queue.remove(task);
  }

  /*任务总数*/
  public synchronized int getTaskSize(){
    return queue.size();
  }

  /*自动装填任务*/
  public synchronized void autoAddTask(){
  
    synchronized(this){
      float load_size_auto = load_size - getTaskSize() / load_size;
      System.out.println("===load_size_auto:"+load_size_auto);
      
      if(load_size_auto &gt; 0.25){        
        autoAddTask0();
      }
      else {
        System.out.println("=== Not must load new work queue ... Now! ");
      }    
    }
  }

  /*删除所有任务*/
  public synchronized void clearAllTask(){
    queue.clear();
  }
  
  /**
   * 程序员自己实现该方法
   */
  protected abstract void autoAddTask0();
}
</pre><hr /></blockquote><br /><br /><br /><br />MyWorkQueue<br />=====================================<br /><blockquote><font size="-1">CODE:</font><hr /><pre>package test.thread.pool1.impl;

import java.util.LinkedList;
import test.thread.pool1.WorkQueue;

/**
 * &lt;p&gt;Title: 例子工作队列对象 &lt;/p&gt;
 * &lt;p&gt;Description: &lt;/p&gt;
 * &lt;p&gt;Copyright: Copyright (c) 2005&lt;/p&gt;
 * &lt;p&gt;Company: &lt;/p&gt;
 * @author not attributable
 * @version 1.0
 */

public class MyWorkQueue extends WorkQueue{

  /**
   * @param load_size 预计装载量
   * @param load_polling_time 数据装载轮循时间
   */
  public MyWorkQueue(int load_size,long load_polling_time){
    super(load_size,load_polling_time);
  }

  /**
   * 自动加载任务
   */
  protected synchronized void autoAddTask0(){
    //-------------------
    System.out.println("===MyWorkQueue ...  invoked autoAddTask0() method ...");
    for(int i=0; i&lt;10; i++){
      System.out.println("===add task :"+i);
      this.addTask(new MyTask());
    }    
    //-------------------
  }
}
</pre><hr /></blockquote><br /><br /><br /><br />MyTask<br />=====================================<br /><blockquote><font size="-1">CODE:</font><hr /><pre>package test.thread.pool1.impl;
import test.thread.pool1.ITask;

/**
 * &lt;p&gt;Title: 工作任务接口 &lt;/p&gt;
 * &lt;p&gt;Description: &lt;/p&gt;
 * &lt;p&gt;Copyright: Copyright (c) 2005&lt;/p&gt;
 * &lt;p&gt;Company: &lt;/p&gt;
 * @author not attributable
 * @version 1.0
 */

public class MyTask implements ITask {

  /**
   * 执行的任务
   * @throws java.lang.Throwable
   */
  public void executeTask() throws Throwable{
    System.out.println("["+this.hashCode()+"] MyTask ... invoked executeTask() method ... ");
  }
}
</pre></blockquote><img src ="http://www.blogjava.net/lijiajia418/aggbug/65562.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 16:55 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65562.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java线程安全</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65534.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:42:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65534.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65534.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65534.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65534.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65534.html</trackback:ping><description><![CDATA[
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">一直不敢写点什么，是因为战战兢兢，生怕写的不好甚至写错了会误人子弟。随笔可以随便写一下，不用太过计较，可是技术从来都要不得半点马虎，差之毫厘，谬以千里啊！但敝帚自珍又不是我的风格，虽然文笔不好，也要勉为其难了。废话少说，进入正题。</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<?XML:NAMESPACE PREFIX = O /?>
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>       </span>
				</span>
				<span style="FONT-FAMILY: 宋体">从我开始接触</span>
				<span lang="EN-US">Java</span>
				<span style="FONT-FAMILY: 宋体">的多线程起就总是觉得书上讲的不是那么清楚。不是说读完了不会写，而是对写出来的多线程代码懵懵懂懂，不知道每一句会有什么影响，心里感觉忐忑。后来仔细研读</span>
				<span lang="EN-US">Java</span>
				<span style="FONT-FAMILY: 宋体">语言规范后，才慢慢搞明白一些细节。我主要想说的，也就是这些经验吧。</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>       </span>
				</span>
				<span style="FONT-FAMILY: 宋体">首先要搞清楚的是线程的共享资源，共享资源是多线程中每个线程都要访问的类变量或实例变量，共享资源可以是单个类变量或实例变量，也可以是一组类变量或实例变量。多线程程序可以有多个共享资源。下面描述他们之间的一对多关系（</span>
				<span lang="EN-US">*</span>
				<span style="FONT-FAMILY: 宋体">表示多）：</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>       </span>
				</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>                     </span>
				</span>
				<span style="FONT-FAMILY: 宋体">多线程程序（</span>
				<span lang="EN-US">1</span>
				<span style="FONT-FAMILY: 宋体">）</span>
				<span lang="EN-US">----</span>
				<span style="FONT-FAMILY: 宋体">共享资源（</span>
				<span lang="EN-US">*</span>
				<span style="FONT-FAMILY: 宋体">）</span>
				<span lang="EN-US">----</span>
				<span style="FONT-FAMILY: 宋体">类变量或实例变量（</span>
				<span lang="EN-US">1…*</span>
				<span style="FONT-FAMILY: 宋体">）</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">只有类变量和实例变量可以成为共享资源，细分如下：</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>1.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">实现线程的类（继承</span><span lang="EN-US">Thread</span><span style="FONT-FAMILY: 宋体">类、实现</span><span lang="EN-US">Throwable</span><span style="FONT-FAMILY: 宋体">接口的类）的类变量、实例变量。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>2.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">实现线程的类的类变量、实例变量的类变量、实例变量，可以不规范的写为：</span><span lang="EN-US">TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*</span><span style="FONT-FAMILY: 宋体">，</span><span lang="EN-US">[]*</span><span style="FONT-FAMILY: 宋体">的内容表示无限可重复。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>3.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">不是实现线程的类，但其对象可能是线程的类变量或实例变量。如</span><span lang="EN-US">Servlet</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">EJB</span><span style="FONT-FAMILY: 宋体">。这些类的类变量和实例变量，不规范的写为：</span><span lang="EN-US">ServletOrEJB.ClassOrInstanceVar[.ClassOrInstanceVar]*</span><span style="FONT-FAMILY: 宋体">。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<b><span lang="EN-US"><span>4.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span></b>&lt;!--[endif]--&gt;<b><span style="FONT-FAMILY: 宋体">特别注意：局部变量、做为参数传递的非类变量、非实例变量不是共享资源。</span><span lang="EN-US"><o:p></o:p></span></b></p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">那么什么是线程安全呢？关于这个问题我在网上百度了一下（没办法，有时候</span>
				<span lang="EN-US">GOOGLE</span>
				<span style="FONT-FAMILY: 宋体">用不了），发现不少人在问这个问题，也有不少错误的理解。所以我给出一个较容易理解的解释：<b>在线程中使用共享资源时，能够保证共享资源在任何时候都是原子的、一致的，这样的线程就是线程安全的线程。</b>还不太理解？没有关系，慢慢解释。</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">首先来介绍一下共享资源的类型（这是我自己分类的，为了后文好解释），共享资源从其类型可以分为三类（下文讲到变量一律指类变量或实例变量，不再特别指出）：</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>1.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">独立的基本类型共享资源，如一个简单的</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">变量，例：</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">public class Cls1 {</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int a;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>public int getA(){return a;}</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>public void setA(int a){this.a = a;}</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">}</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span style="FONT-FAMILY: 宋体">可以看到</span>
				<span lang="EN-US">a</span>
				<span style="FONT-FAMILY: 宋体">没有任何依赖。</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">public class Cls2{</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int a;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int b;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int c;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>// </span>
				<span style="FONT-FAMILY: 宋体">没有对</span>
				<span lang="EN-US">a</span>
				<span style="FONT-FAMILY: 宋体">的访问方法，</span>
				<span lang="EN-US">a</span>
				<span style="FONT-FAMILY: 宋体">在</span>
				<span lang="EN-US">Cls</span>
				<span style="FONT-FAMILY: 宋体">外不可见。</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">}</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span style="FONT-FAMILY: 宋体">假设上面类中</span>
				<span lang="EN-US">b</span>
				<span style="FONT-FAMILY: 宋体">、</span>
				<span lang="EN-US">c</span>
				<span style="FONT-FAMILY: 宋体">都不依赖</span>
				<span lang="EN-US">a</span>
				<span style="FONT-FAMILY: 宋体">，则</span>
				<span lang="EN-US">a</span>
				<span style="FONT-FAMILY: 宋体">是这种类型。</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>2.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">相互依赖的基本类型共享资源，一个类中的几个基本类型变量互相依赖，但从对象设计的角度又不能单独把这几个变量设计成一个类。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span style="FONT-FAMILY: 宋体">假设上例</span>
				<span lang="EN-US">Cls2</span>
				<span style="FONT-FAMILY: 宋体">中的</span>
				<span lang="EN-US">b</span>
				<span style="FONT-FAMILY: 宋体">、</span>
				<span lang="EN-US">c</span>
				<span style="FONT-FAMILY: 宋体">互相依赖，则属此种情况。</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>3.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span lang="EN-US">64</span><span style="FONT-FAMILY: 宋体">位的基本类型变量。这个比较特殊，因为某些机器上</span><span lang="EN-US">64</span><span style="FONT-FAMILY: 宋体">变量会分成两个</span><span lang="EN-US">32</span><span style="FONT-FAMILY: 宋体">位的操作，所以和</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">不一样。如</span><span lang="EN-US">double</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">long</span><span style="FONT-FAMILY: 宋体">类型。</span><span lang="EN-US"><o:p></o:p></span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>4.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">类类型的共享资源。如下例中的</span><span lang="EN-US">obj</span><span style="FONT-FAMILY: 宋体">：</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">public class Cls3{</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private SomeObj obj;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">}</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">public class SomeObj{</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int a;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">
						<span>       </span>private int b;</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 63pt">
				<span lang="EN-US">}</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>       </span>
				</span>
				<span style="FONT-FAMILY: 宋体">其次来看看什么是原子性、一致性。其实在这里我借用了事务</span>
				<span lang="EN-US">ACID</span>
				<span style="FONT-FAMILY: 宋体">属性的</span>
				<span lang="EN-US">A</span>
				<span style="FONT-FAMILY: 宋体">和</span>
				<span lang="EN-US">C</span>
				<span style="FONT-FAMILY: 宋体">，熟悉的朋友就不用我废话了。所谓原子性，是指一个共享资源的所有属性在任何时刻都是一起变化、密不可分的；所谓一致性，是指一个共享资源的所有属性在变化之后一定会达到一个一致的状态。</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<span>       </span>
				</span>
				<span style="FONT-FAMILY: 宋体">最后根据上述四种共享资源类型，来看看如何做到线程安全。</span>
		</p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>1.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">不用做什么，只一个独立的变量，任何时候它都是原子、一致的。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>2.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">使用</span><span lang="EN-US">synchronized</span><span style="FONT-FAMILY: 宋体">关键字，保证几个变量被一起修改、一起读取。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>3.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">使用</span><span lang="EN-US">volatile</span><span style="FONT-FAMILY: 宋体">关键字，然后就和</span><span lang="EN-US">1</span><span style="FONT-FAMILY: 宋体">一样了。</span></p>
		<p class="MsoNormal" style="MARGIN-LEFT: 39pt; TEXT-INDENT: -18pt">&lt;!--[if !supportLists]--&gt;<span lang="EN-US"><span>4.<span style="FONT: 7pt 'Times New Roman'; font-size-adjust: none; font-stretch: normal">       </span></span></span>&lt;!--[endif]--&gt;<span style="FONT-FAMILY: 宋体">和</span><span lang="EN-US">2</span><span style="FONT-FAMILY: 宋体">一样处理。</span></p>
		<p class="MsoNormal">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">当对访问共享资源的方法不同时使用</span>
				<span lang="EN-US">synchronized</span>
				<span style="FONT-FAMILY: 宋体">关键字时，是什么样一种情况呢？这是需要<b>特别注意</b>的，<b>这样不能保证线程安全！</b>看看下面例子的运行结果就知道了（自己运行啊，我不贴结果了）：</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">/**</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Author: $</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Date: $</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Revision: $</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $History: $ </span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>*</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* Created by feelyou, at time </span>
				<?XML:NAMESPACE PREFIX = ST1 /?>
				<st1:time minute="31" hour="22">
						<span lang="EN-US">22:31:53</span>
				</st1:time>
				<span lang="EN-US">, 2005-11-16.</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>*/</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">public class TestThread extends Thread {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int a = 0;</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int b = 0;</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public static void main(String[] args) {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>TestThread test = new TestThread();</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>for (int i = 0; i &lt; 10; i++) {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>Thread thread = new Thread(test, "thread-" + i);</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>thread.start();</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public synchronized void doWrite() {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>a++;</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>try {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>sleep((int)(Math.random()*100));</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>catch (InterruptedException e) {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>b++;</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>try {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>sleep((int)(Math.random()*100));</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>catch (InterruptedException e) {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public void print() {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>System.out.println("" + Thread.currentThread().getName() + ":a:" + a);</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>System.out.println("" + Thread.currentThread().getName() + ":b:" + b);</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public void run() {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>super.run();<span>    </span>//To change body of overridden methods use File | Settings | File Templates.</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>for (int i = 0; i &lt; 10; i++) {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>doWrite();</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>print();</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public synchronized void start() {</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>super.start();<span>    </span>//To change body of overridden methods use File | Settings | File Templates.</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">}</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21.1pt">
				<b>
						<span lang="EN-US">ThreadLocal</span>
				</b>
				<b>
						<span style="FONT-FAMILY: 宋体">？</span>
				</b>
				<span lang="EN-US">ThreadLocal</span>
				<span style="FONT-FAMILY: 宋体">对于线程安全还是很有用的，如果资源不是共享的，那么应该使用</span>
				<span lang="EN-US">ThreadLocal</span>
				<span style="FONT-FAMILY: 宋体">，但如果确实需要在线程间共享资源，</span>
				<span lang="EN-US">ThreadLocal</span>
				<span style="FONT-FAMILY: 宋体">就没有用了！</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体">最后，来一个完整的线程安全的例子：</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">/**<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Author: $<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Date: $<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $Revision: $<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* $History: $ <o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>*<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>* Created by feelyou, at time </span>
				<st1:time minute="31" hour="22">
						<span lang="EN-US">22:31:53</span>
				</st1:time>
				<span lang="EN-US">, 2005-11-16.<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span> </span>*/<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">public class TestThread extends Thread {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int a = 0; //</span>
				<span style="FONT-FAMILY: 宋体">独立的共享资源</span>
				<span lang="EN-US">
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int b = 0; //b</span>
				<span style="FONT-FAMILY: 宋体">、</span>
				<span lang="EN-US">c</span>
				<span style="FONT-FAMILY: 宋体">互相依赖</span>
				<span lang="EN-US">
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int c = 0;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private volatile long d = 0L; //64</span>
				<span style="FONT-FAMILY: 宋体">位</span>
				<span lang="EN-US">
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">//<span>  </span>private SomeObj obj = new SomeObj(); //</span>
				<span style="FONT-FAMILY: 宋体">对象类型，大家自己写吧，我就不写了。</span>
				<span lang="EN-US">
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public static void main(String[] args) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>TestThread test = new TestThread();<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>for (int i = 0; i &lt; 10; i++) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>Thread thread = new Thread(test, "thread-" + i);<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>thread.start();<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public synchronized void doWrite() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>b++;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>try {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>sleep((int)(Math.random()*100));<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>catch (InterruptedException e) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>c++;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>try {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>   </span>
						<span>   </span>sleep((int)(Math.random()*100));<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>catch (InterruptedException e) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public synchronized void print() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>System.out.println("" + Thread.currentThread().getName() + ":b:" + b);<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>System.out.println("" + Thread.currentThread().getName() + ":c:" + c);<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private void setA(int a) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<span>    </span>this.a = a;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>private int getA() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<span>    </span>return a;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public long getD() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<span>    </span>return d;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<o:p>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public void setD(long d) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>
						<span>    </span>this.d = d;<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public void run() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>super.run();<span>    </span>//To change body of overridden methods use File | Settings | File Templates.<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>for (int i = 0; i &lt; 10; i++) {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>doWrite();<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>print();<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>setA(i);<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>System.out.println(getA());<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>setD(18456187413L * i);<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>      </span>System.out.println(getD());<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<o:p> </o:p>
				</span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>public synchronized void start() {<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>    </span>super.start();<span>    </span>//To change body of overridden methods use File | Settings | File Templates.<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<span>  </span>}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="TEXT-INDENT: 21pt">
				<span lang="EN-US">}<br /></span>
		</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65534.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:42 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65534.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java5泛型的用法，T.class的获取和为擦拭法站台</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65530.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:22:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65530.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65530.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65530.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65530.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65530.html</trackback:ping><description><![CDATA[Java 5的泛型语法已经有太多书讲了，这里不再打字贴书。GP一定有用，不然Java和C#不会约好了似的同时开始支持GP。但大家也清楚，GP和Ruby式的动态OO语言属于不同的意识形态，如果是一人一票，我想大部分的平民程序员更热衷动态OO语言的平白自然。但如果不准备跳槽到支持JSR223的动态语言，那还是看看GP吧。<br /><br />   <strong>胡乱总结泛型的四点作用：<br /></strong>   第一是泛化，可以拿个T代表任意类型。 但GP是被C++严苛的静态性逼出来的，落到Java、C#这样的花语平原里----所有对象除几个原始类型外都派生于Object，再加上Java的反射功能，Java的Collection库没有范型一样过得好好的。<br /><br />   第二是泛型 + 反射，原本因为Java的泛型拿不到T.class而觉得泛型没用，最近才刚刚学到通过反射的API来获取T的Class，后述。<br /><br />   第三是收敛，就是增加了类型安全，减少了强制类型转换的代码。这点倒是Java Collection历来的弱项。<br /><br />   第四是可以在编译期搞很多东西，比如MetaProgramming。但除非能完全封闭于框架内部，框架的使用者和扩展者都不用学习这些东西的用法，否则那就是自绝于人民的票房毒药。C++的MetaProgramming好厉害吧，但对比一下Python拿Meta Programming生造一个Class出来的简便语法，就明白什么才是真正的叫好又叫座。<br /><br />   所以，作为一个架构设计师，应该使用上述的第2，3项用法，在框架类里配合使用反射和泛型，使得框架的能力更强； 同时采用收敛特性，本着对人民负责的精神，用泛型使框架更加类型安全，更少强制类型转换。<br />   <br />   <strong>擦拭法避免了Java的流血分裂 ：</strong><br />    大家经常骂Java GP的擦拭法实现，但我觉得多亏于它的中庸特性---如果你用就是范型，不用就是普通Object，避免了Java阵营又要经历一场to be or not to be的分裂。 <br />    最大的例子莫过Java 5的Collection 框架， 比如有些同学坚持认为自己不会白痴到类型出错，而且难以忍受每个定义的地方都要带一个泛型定义List〈Book〉<goods>，不用强制类型转换所省下的代码还不够N处定义花的(对了，java里面还没有tyepdef.....)，因此对范型十分不感冒，这时就要齐齐感谢这个搽拭法让你依然可以对一个泛型框架保持非泛型的用法了...<br /><br />  <strong> 通过反射获得 T.class：</strong><br />    <br />    不知为何书上不怎么讲这个，是差沙告诉我才知道的，最经典的应用见Hibernate wiki的<a href="http://www.hibernate.org/328.html"><font color="#366900">Generic Data Access Objects</font></a>， 代码如下：  
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">abstract</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000"> BaseHibernateEntityDao</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">T</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">extends</span><span style="COLOR: #000000"> HibernateDaoSupport {<br /> </span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000"> Class&lt;T&gt; entityClass;<br /> </span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> BaseHibernateEntityDao() {<br />        entityClass </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">(Class&lt;T&gt;) ((ParameterizedType) getClass()<br />                                .getGenericSuperclass()).getActualTypeArguments()[0];<br />    }<br /> </span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> T get(Serializable id) {<br />        T o </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (T) getHibernateTemplate().get(entityClass, id);<br />}<br />}</span></div><br />  精华就是这句了：<br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">Class</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">T</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"> entityClass </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (Class</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">T</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">]; </span></div><br />  泛型之后，所有BaseHibernateEntityDao的子类只要定义了泛型，就无需再重载getEnttityClass()，get()函数和find()函数，销益挺明显的，所以<a href="http://www.springside.org.cn/"><font color="#366900">SpringSide</font></a>的Dao基类毫不犹豫就泛型了。<br /><br />  不过擦拭法的大棒仍在，所以子类的泛型语法可不能乱写，最正确的用法只有：<br /></goods>   
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000"> BookDao </span><span style="COLOR: #0000ff">extends</span><span style="COLOR: #000000"> BaseHibernateEntityDao</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">Book</span><span style="COLOR: #000000">&gt;</span></div><p> </p><img src ="http://www.blogjava.net/lijiajia418/aggbug/65530.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:22 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65530.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java中对象的排序</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65529.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:21:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65529.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65529.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65529.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65529.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65529.html</trackback:ping><description><![CDATA[
		<p>1：排序类<br /><br />package com.tixa.bad.customer.util;</p>
		<p>import java.util.ArrayList;<br />import java.util.Collections;<br />import java.util.Comparator;</p>
		<p>import com.tixa.bad.customer.data.AdstatGraphArea;</p>
		<p>/**<br /> * 对地区数据进行排序<br /> * <br /> * @StatAreaSort<br /> * <br /> * @author <br /> * <br /> * TODO<br /> */<br />public class StatAreaSort<br />{<br /> /**<br />  * 点击排序规则，倒序<br />  */<br /> private static Comparator orderClick = new Comparator()<br /> {<br />  public int compare(Object o1, Object o2)<br />  {<br />   AdstatGraphArea b1 = (AdstatGraphArea) o1;<br />   AdstatGraphArea b2 = (AdstatGraphArea) o2;<br />   return (b2.getClickcount() - b1.getClickcount());<br />  }<br /> };</p>
		<p> /**<br />  * ip排序规则，倒序<br />  */<br /> private static Comparator orderIP = new Comparator()<br /> {<br />  public int compare(Object o1, Object o2)<br />  {<br />   AdstatGraphArea b1 = (AdstatGraphArea) o1;<br />   AdstatGraphArea b2 = (AdstatGraphArea) o2;<br />   return (b2.getIpcount() - b1.getIpcount());<br />  }<br /> };</p>
		<p> /**<br />  * 根据点击量进行排序<br />  * <br />  * @param list<br />  */<br /> public static void getSortClick(ArrayList list)<br /> {<br />  Collections.sort(list, orderClick);<br />  // Collections.reverse(list);<br /> }</p>
		<p> /**<br />  * 根据ip量进行排序<br />  * <br />  * @param list<br />  */<br /> public static void getSortIp(ArrayList list)<br /> {<br />  Collections.sort(list, orderIP);<br />  // Collections.reverse(list);<br /> }</p>
		<p>}<br />2：对排序进行测试<br />package com.tixa.bad.customer.util;</p>
		<p>import java.util.ArrayList;</p>
		<p>import junit.framework.TestCase;</p>
		<p>import com.tixa.bad.customer.data.AdstatGraphArea;</p>
		<p>public class StatAreaSortTest extends TestCase<br />{</p>
		<p> protected void setUp() throws Exception<br /> {<br />  super.setUp();<br /> }</p>
		<p> protected void tearDown() throws Exception<br /> {<br />  super.tearDown();<br /> }</p>
		<p> /*<br />  * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortClick(ArrayList)'<br />  */<br /> public void testGetSortClick()<br /> {</p>
		<p>   ArrayList list = new ArrayList();<br />   <br />   AdstatGraphArea graAre = new AdstatGraphArea();<br />   graAre.setAdid(1);<br />   graAre.setClickcount(786);<br />   graAre.setIpcount(43453);<br />   list.add(graAre);<br />   <br />   AdstatGraphArea graAre1 = new AdstatGraphArea();<br />   graAre1.setAdid(2);<br />   graAre1.setClickcount(987876);<br />   graAre1.setIpcount(545);<br />   list.add(graAre1);<br />   <br />   AdstatGraphArea graAre2 = new AdstatGraphArea();<br />   graAre2.setAdid(3);<br />   graAre2.setClickcount(877887);<br />   graAre2.setIpcount(4534534);<br />   list.add(graAre2);<br />   <br />   AdstatGraphArea graAre3 = new AdstatGraphArea();<br />   graAre3.setAdid(4);<br />   graAre3.setClickcount(97998);<br />   graAre3.setIpcount(34534);<br />   list.add(graAre3);<br />   <br />   AdstatGraphArea graAre4 = new AdstatGraphArea();<br />   graAre4.setAdid(5);<br />   graAre4.setClickcount(500);<br />   graAre4.setIpcount(2000);<br />   list.add(graAre4);<br />   <br />   System.out.print("sore before ");<br />   for(int i = 0;i&lt;list.size();i++)<br />   {<br />    AdstatGraphArea a = (AdstatGraphArea)list.get(i);<br />    System.out.println(a.getAdid());<br />   }<br />   <br />   StatAreaSort.getSortClick(list);<br />   <br />   System.out.print("sore after");<br />   for(int i = 0;i&lt;list.size();i++)<br />   {<br />    AdstatGraphArea a = (AdstatGraphArea)list.get(i);<br />    System.out.println(a.getAdid());<br />   }<br />   System.out.println("----------------------");<br />   <br />   StatAreaSort.getSortIp(list);<br />   for(int i = 0;i&lt;list.size();i++)<br />   {<br />    AdstatGraphArea a = (AdstatGraphArea)list.get(i);<br />    System.out.println(a.getAdid());<br />   }<br /> }</p>
		<p> /*<br />  * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortIp(ArrayList)'<br />  */<br /> public void testGetSortIp()<br /> {</p>
		<p> }</p>}<img src ="http://www.blogjava.net/lijiajia418/aggbug/65529.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:21 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65529.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java程序性能优化</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65527.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:20:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65527.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65527.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65527.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65527.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65527.html</trackback:ping><description><![CDATA[
		<div class="postText">
				<h3>
						<font color="#ff0000">一、避免在循环条件中使用复杂表达式</font>
				</h3>
				<br />在不做编译优化的情况下，在循环中，循环条件会被反复计算，如果不使用复杂表达式，而使循环条件值不变的话，程序将会运行的更快。<br /><br />例子：<br />import java.util.Vector;<br />class CEL {<br />    void method (Vector vector) {<br />        for (int i = 0; i &lt; vector.size (); i++)  // Violation<br />            ; // ...<br />    }<br />}<br /><br />更正：<br />class CEL_fixed {<br />    void method (Vector vector) {<br />        int size = vector.size ()<br />        for (int i = 0; i &lt; size; i++)<br />            ; // ...<br />    }<br />}<br /><br /><h3><font color="#ff0000">二、为'Vectors' </font>和 'Hashtables'定义初始大小</h3><br />JVM为Vector扩充大小的时候需要重新创建一个更大的数组，将原原先数组中的内容复制过来，最后，原先的数组再被回收。可见Vector容量的扩大是一个颇费时间的事。<br />通常，默认的10个元素大小是不够的。你最好能准确的估计你所需要的最佳大小。<br /><br />例子：<br />import java.util.Vector;<br />public class DIC {<br />    public void addObjects (Object[] o) {<br />        // if length &gt; 10, Vector needs to expand <br />        for (int i = 0; i&lt; o.length;i++) {    <br />            v.add(o);   // capacity before it can add more elements.<br />        }<br />    }<br />    public Vector v = new Vector();  // no initialCapacity.<br />}<br /><br />更正：<br />自己设定初始大小。<br />    public Vector v = new Vector(20);  <br />    public Hashtable hash = new Hashtable(10); <br /><br />参考资料：<br />Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming <br />Techniques" Addison Wesley, ISBN: 0-201-70429-3 pp.55 – 57<br /><br /><h3><font color="#ff0000">三、在finally</font>块中关闭Stream</h3><br />程序中使用到的资源应当被释放，以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何，finally块总是会执行的，以确保资源的正确关闭。<br />         <br />例子：<br />import java.io.*;<br />public class CS {<br />    public static void main (String args[]) {<br />        CS cs = new CS ();<br />        cs.method ();<br />    }<br />    public void method () {<br />        try {<br />            FileInputStream fis = new FileInputStream ("CS.java");<br />            int count = 0;<br />            while (fis.read () != -1)<br />                count++;<br />            System.out.println (count);<br />            fis.close ();<br />        } catch (FileNotFoundException e1) {<br />        } catch (IOException e2) {<br />        }<br />    }<br />}<br />         <br />更正：<br />在最后一个catch后添加一个finally块<br /><br />参考资料：<br />Peter Haggar: "Practical Java - Programming Language Guide".<br />Addison Wesley, 2000, pp.77-79<br /><h3><font color="#ff0000">四、使用'System.arraycopy ()'</font>代替通过来循环复制数组</h3><br />'System.arraycopy ()' 要比通过循环来复制数组快的多。<br />         <br />例子：<br />public class IRB<br />{<br />    void method () {<br />        int[] array1 = new int [100];<br />        for (int i = 0; i &lt; array1.length; i++) {<br />            array1 [i] = i;<br />        }<br />        int[] array2 = new int [100];<br />        for (int i = 0; i &lt; array2.length; i++) {<br />            array2 [i] = array1 [i];                 // Violation<br />        }<br />    }<br />}<br />         <br />更正：<br />public class IRB<br />{<br />    void method () {<br />        int[] array1 = new int [100];<br />        for (int i = 0; i &lt; array1.length; i++) {<br />            array1 [i] = i;<br />        }<br />        int[] array2 = new int [100];<br />        System.arraycopy(array1, 0, array2, 0, 100);<br />    }<br />}<br />         <br />参考资料：<br />http://www.cs.cmu.edu/~jch/java/speed.html<br /><br /><h3><font color="#ff0000">五、让访问实例内变量的getter/setter</font>方法变成”final”</h3><br />简单的getter/setter方法应该被置成final，这会告诉编译器，这个方法不会被重载，所以，可以变成”inlined”<br /><br />例子：<br />class MAF {<br />    public void setSize (int size) {<br />         _size = size;<br />    }<br />    private int _size;<br />}<br /><br />更正：<br />class DAF_fixed {<br />    final public void setSize (int size) {<br />         _size = size;<br />    }<br />    private int _size;<br />}<br /><br />参考资料：<br />Warren N. and Bishop P. (1999), "Java in Practice", p. 4-5<br />Addison-Wesley, ISBN 0-201-36065-9<br /><br /><h3><font color="#ff0000">六、避免不需要的instanceof</font>操作</h3><br />如果左边的对象的静态类型等于右边的，instanceof表达式返回永远为true。<br />         <br />例子：         <br />public class UISO {<br />    public UISO () {}<br />}<br />class Dog extends UISO {<br />    void method (Dog dog, UISO u) {<br />        Dog d = dog;<br />        if (d instanceof UISO) // always true.<br />            System.out.println("Dog is a UISO"); <br />        UISO uiso = u;<br />        if (uiso instanceof Object) // always true.<br />            System.out.println("uiso is an Object"); <br />    }<br />}<br />         <br />更正：         <br />删掉不需要的instanceof操作。<br />         <br />class Dog extends UISO {<br />    void method () {<br />        Dog d;<br />        System.out.println ("Dog is an UISO");<br />        System.out.println ("UISO is an UISO");<br />    }<br />}<br /><br /><h3><font color="#ff0000">七、避免不需要的造型操作</font></h3><br />所有的类都是直接或者间接继承自Object。同样，所有的子类也都隐含的“等于”其父类。那么，由子类造型至父类的操作就是不必要的了。<br />例子：<br />class UNC {<br />    String _id = "UNC";<br />}<br />class Dog extends UNC {<br />    void method () {<br />        Dog dog = new Dog ();<br />        UNC animal = (UNC)dog;  // not necessary.<br />        Object o = (Object)dog;         // not necessary.<br />    }<br />}<br />         <br />更正：         <br />class Dog extends UNC {<br />    void method () {<br />        Dog dog = new Dog();<br />        UNC animal = dog;<br />        Object o = dog;<br />    }<br />}<br />         <br />参考资料：<br />Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms<br />for Effective Java".  Addison-Wesley, 1999. pp.22-23<br /><h3><font color="#ff0000">八、如果只是查找单个字符的话，用charAt()</font>代替startsWith()</h3><br />用一个字符作为参数调用startsWith()也会工作的很好，但从性能角度上来看，调用用String API无疑是错误的!<br />         <br />例子：<br />public class PCTS {<br />    private void method(String s) {<br />        if (s.startsWith("a")) { // violation<br />            // ...<br />        }<br />    }<br />}<br />         <br />更正         <br />将'startsWith()' 替换成'charAt()'.<br />public class PCTS {<br />    private void method(String s) {<br />        if ('a' == s.charAt(0)) {<br />            // ...<br />        }<br />    }<br />}<br />         <br />参考资料：<br />Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming <br />Techniques"  Addison Wesley, ISBN: 0-201-70429-3<br /><h3><font color="#ff0000">九、使用移位操作来代替'a / b'</font>操作 </h3><br />"/"是一个很“昂贵”的操作，使用移位操作将会更快更有效。<br /><br />例子：<br />public class SDIV {<br />    public static final int NUM = 16;<br />    public void calculate(int a) {<br />        int div = a / 4;            // should be replaced with "a &gt;&gt; 2".<br />        int div2 = a / 8;         // should be replaced with "a &gt;&gt; 3".<br />        int temp = a / 3;<br />    }<br />}<br /><br />更正：<br />public class SDIV {<br />    public static final int NUM = 16;<br />    public void calculate(int a) {<br />        int div = a &gt;&gt; 2;  <br />        int div2 = a &gt;&gt; 3; <br />        int temp = a / 3;       // 不能转换成位移操作<br />    }<br />}<br /><br /><h3><font color="#ff0000">十、使用移位操作代替'a * b' </font></h3><br />同上。<br />[i]但我个人认为，除非是在一个非常大的循环内，性能非常重要，而且你很清楚你自己在做什么，方可使用这种方法。否则提高性能所带来的程序晚读性的降低将是不合算的。<br /><br />例子：<br />public class SMUL {<br />    public void calculate(int a) {<br />        int mul = a * 4;            // should be replaced with "a &lt;&lt; 2".<br />        int mul2 = 8 * a;         // should be replaced with "a &lt;&lt; 3".<br />        int temp = a * 3;<br />    }<br />}<br /><br />更正：<br />package OPT;<br />public class SMUL {<br />    public void calculate(int a) {<br />        int mul = a &lt;&lt; 2;  <br />        int mul2 = a &lt;&lt; 3; <br />        int temp = a * 3;       // 不能转换<br />    }<br />}<br /><br /><h3><font color="#ff0000">十一、在字符串相加的时候，使用 ' ' </font>代替 " "，如果该字符串只有一个字符的话 </h3><br /><br />例子：<br />public class STR {<br />    public void method(String s) {<br />        String string = s + "d"  // violation.<br />        string = "abc" + "d"      // violation.<br />    }<br />}<br /><br />更正：<br />将一个字符的字符串替换成' '<br />public class STR {<br />    public void method(String s) {<br />        String string = s + 'd'<br />        string = "abc" + 'd'   <br />    }<br />}<br /><br /><h3><font color="#ff0000">十二、不要在循环中调用synchronized(</font>同步)方法 </h3><br />方法的同步需要消耗相当大的资料，在一个循环中调用它绝对不是一个好主意。<br /><br />例子：<br />import java.util.Vector;<br />public class SYN {<br />    public synchronized void method (Object o) {<br />    }<br />    private void test () {<br />        for (int i = 0; i &lt; vector.size(); i++) {<br />            method (vector.elementAt(i));    // violation<br />        }<br />    }<br />    private Vector vector = new Vector (5, 5);<br />}<br /><br />更正：<br />不要在循环体中调用同步方法，如果必须同步的话，推荐以下方式：<br />import java.util.Vector;<br />public class SYN {<br />    public void method (Object o) {<br />    }<br />private void test () {<br />    synchronized{//在一个同步块中执行非同步方法<br />            for (int i = 0; i &lt; vector.size(); i++) {<br />                method (vector.elementAt(i));   <br />            }<br />        }<br />    }<br />    private Vector vector = new Vector (5, 5);<br />}<br /><br /><h3><font color="#ff0000">十三、将try/catch</font>块移出循环</h3><br />把try/catch块放入循环体内，会极大的影响性能，如果编译JIT被关闭或者你所使用的是一个不带JIT的JVM，性能会将下降21%之多!<br />         <br />例子：         <br />import java.io.FileInputStream;<br />public class TRY {<br />    void method (FileInputStream fis) {<br />        for (int i = 0; i &lt; size; i++) {<br />            try {                                      // violation<br />                _sum += fis.read();<br />            } catch (Exception e) {}<br />        }<br />    }<br />    private int _sum;<br />}<br />         <br />更正：         <br />将try/catch块移出循环         <br />    void method (FileInputStream fis) {<br />        try {<br />            for (int i = 0; i &lt; size; i++) {<br />                _sum += fis.read();<br />            }<br />        } catch (Exception e) {}<br />    }<br />         <br />参考资料：<br />Peter Haggar: "Practical Java - Programming Language Guide".<br />Addison Wesley, 2000, pp.81 – 83<br /><br /><h3><font color="#ff0000">十四、对于boolean</font>值，避免不必要的等式判断</h3><br />将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值). 移走对于boolean的不必要操作至少会带来2个好处：<br />1)代码执行的更快 (生成的字节码少了5个字节)；<br />2)代码也会更加干净 。<br /><br />例子：<br />public class UEQ<br />{<br />    boolean method (String string) {<br />        return string.endsWith ("a") == true;   // Violation<br />    }<br />}<br /><br />更正：<br />class UEQ_fixed<br />{<br />    boolean method (String string) {<br />        return string.endsWith ("a");<br />    }<br />}<br /><br /><h3><font color="#ff0000">十五、对于常量字符串，用'String' </font>代替 'StringBuffer' </h3><br />常量字符串并不需要动态改变长度。<br />例子：<br />public class USC {<br />    String method () {<br />        StringBuffer s = new StringBuffer ("Hello");<br />        String t = s + "World!";<br />        return t;<br />    }<br />}<br /><br />更正：<br />把StringBuffer换成String，如果确定这个String不会再变的话，这将会减少运行开销提高性能。<br /><br /><h3><font color="#ff0000">十六、用'StringTokenizer' </font>代替 'indexOf()' 和'substring()' </h3><br />字符串的分析在很多应用中都是常见的。使用indexOf()和substring()来分析字符串容易导致StringIndexOutOfBoundsException。而使用StringTokenizer类来分析字符串则会容易一些，效率也会高一些。<br /><br />例子：<br />public class UST {<br />    void parseString(String string) {<br />        int index = 0;<br />        while ((index = string.indexOf(".", index)) != -1) {<br />            System.out.println (string.substring(index, string.length()));<br />        }<br />    }<br />}<br /><br />参考资料：<br />Graig Larman, Rhett Guthrie: "Java 2 Performance and Idiom Guide"<br />Prentice Hall PTR, ISBN: 0-13-014260-3 pp. 282 – 283<br /><br /><h3><font color="#ff0000">十七、使用条件操作符替代"if (cond) return; else return;" </font>结构</h3><br />条件操作符更加的简捷<br />例子：<br />public class IF {<br />    public int method(boolean isDone) {<br />        if (isDone) { <br />            return 0;<br />        } else {<br />            return 10;<br />        }<br />    }<br />}<br /><br />更正：<br />public class IF {<br />    public int method(boolean isDone) {<br />        return (isDone ? 0 : 10);<br />    }<br />}<br /><h3><font color="#ff0000">十八、使用条件操作符代替"if (cond) a = b; else a = c;" </font>结构</h3><br />例子：<br />public class IFAS {<br />    void method(boolean isTrue) {<br />        if (isTrue) { <br />            _value = 0;<br />        } else {<br />            _value = 1;<br />        }<br />    }<br />    private int _value = 0;<br />}<br /><br />更正：<br />public class IFAS {<br />    void method(boolean isTrue) {<br />        _value = (isTrue ? 0 : 1);       // compact expression.<br />    }<br />    private int _value = 0;<br />}<br /><br /><h3><font color="#ff0000">十九、不要在循环体中实例化变量</font></h3><br />在循环体中实例化临时变量将会增加内存消耗<br /><br />例子：         <br />import java.util.Vector;<br />public class LOOP {<br />    void method (Vector v) {<br />        for (int i=0;i &lt; v.size();i++) {<br />            Object o = new Object();<br />            o = v.elementAt(i);<br />        }<br />    }<br />}<br />         <br />更正：         <br />在循环体外定义变量，并反复使用         <br />import java.util.Vector;<br />public class LOOP {<br />    void method (Vector v) {<br />        Object o;<br />        for (int i=0;i&lt;v.size();i++) {<br />            o = v.elementAt(i);<br />        }<br />    }<br />}<br /><br /><h3><font color="#ff0000">二十、确定 StringBuffer</font>的容量</h3><br />StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中，如果超出这个大小，就会重新分配内存，创建一个更大的数组，并将原先的数组复制过来，再丢弃旧的数组。在大多数情况下，你可以在创建StringBuffer的时候指定大小，这样就避免了在容量不够的时候自动增长，以提高性能。<br /><br />例子：         <br />public class RSBC {<br />    void method () {<br />        StringBuffer buffer = new StringBuffer(); // violation<br />        buffer.append ("hello");<br />    }<br />}<br />         <br />更正：         <br />为StringBuffer提供寝大小。         <br />public class RSBC {<br />    void method () {<br />        StringBuffer buffer = new StringBuffer(MAX);<br />        buffer.append ("hello");<br />    }<br />    private final int MAX = 100;<br />}<br />         <br />参考资料：<br />Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming <br />Techniques" Addison Wesley, ISBN: 0-201-70429-3 p.30 – 31<br /><br /><h3><font color="#ff0000">二十一、尽可能的使用栈变量</font></h3><br />如果一个变量需要经常访问，那么你就需要考虑这个变量的作用域了。static? local?还是实例变量？访问静态变量和实例变量将会比访问局部变量多耗费2-3个时钟周期。<br />         <br />例子：<br />public class USV {<br />    void getSum (int[] values) {<br />        for (int i=0; i &lt; value.length; i++) {<br />            _sum += value[i];           // violation.<br />        }<br />    }<br />    void getSum2 (int[] values) {<br />        for (int i=0; i &lt; value.length; i++) {<br />            _staticSum += value[i];<br />        }<br />    }<br />    private int _sum;<br />    private static int _staticSum;<br />}     <br />         <br />更正：         <br />如果可能，请使用局部变量作为你经常访问的变量。<br />你可以按下面的方法来修改getSum()方法：         <br />void getSum (int[] values) {<br />    int sum = _sum;  // temporary local variable.<br />    for (int i=0; i &lt; value.length; i++) {<br />        sum += value[i];<br />    }<br />    _sum = sum;<br />}<br />         <br />参考资料：         <br />Peter Haggar: "Practical Java - Programming Language Guide".<br />Addison Wesley, 2000, pp.122 – 125<br /><br /><h3><font color="#ff0000">二十二、不要总是使用取反操作符(!)</font></h3><br />取反操作符(!)降低程序的可读性，所以不要总是使用。<br /><br />例子：<br />public class DUN {<br />    boolean method (boolean a, boolean b) {<br />        if (!a)<br />            return !a;<br />        else<br />            return !b;<br />    }<br />}<br /><br />更正：<br />如果可能不要使用取反操作符(!)<br /><br /><h3><font color="#ff0000">二十三、与一个接口 </font>进行instanceof操作</h3><br />基于接口的设计通常是件好事，因为它允许有不同的实现，而又保持灵活。只要可能，对一个对象进行instanceof操作，以判断它是否某一接口要比是否某一个类要快。<br /><br />例子：<br />public class INSOF {<br />    private void method (Object o) {<br />        if (o instanceof InterfaceBase) { }  // better<br />        if (o instanceof ClassBase) { }   // worse.<br />    }<br />}<br /><br />class ClassBase {}<br />interface InterfaceBase {}<br /></div>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65527.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:20 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65527.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java生成UUID</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65525.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:17:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65525.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65525.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65525.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65525.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65525.html</trackback:ping><description><![CDATA[ UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字，它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算，用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分的组合：当前日期和时间(UUID的第一个部分与时间有关，如果你在生成一个UUID之后，过几秒又生成一个UUID，则第一个部分不同，其余相同)，时钟序列，全局唯一的IEEE机器识别号（如果有网卡，从网卡获得，没有网卡以其他方式获得），UUID的唯一缺陷在于生成的结果串会比较长。<br /><br />在Java中生成UUID主要有以下几种方式:<br /><br />1. JDK1.5<br />如果使用的JDK1.5的话,那么生成UUID变成了一件简单的事,以为JDK实现了UUID: java.util.UUID,直接调用即可.<br />UUID uuid  =  UUID.randomUUID();<br /><br />2. 第三方开源类库(推荐使用):<br />最著名的是 JUG .特点上是: 纯Java实现，开源，LGPL协议。采用了Native的方式产生真正的Uuid.而且提供了不同平台的实现,包括:<br /><br />    * Linux / x86<br />    * Windows (98, ME, NT, 2K, XP?) / x86<br />    * Solaris / Sparc<br />    * Mac OS X<br />    * FreeBSD / x86<br /><br />import  org.doomdark.uuid.UUID;<br />import  org.doomdark.uuid.UUIDGenerator;<br /><br />UUIDGenerator generator  =  UUIDGenerator.getInstance();<br />UUID uuid  =  generator.generateRandomBasedUUID();<br /><br />3. Java代码实现:<br />如果你使用JDK1.4或以前版本,又不想加入第三方的类库的话,下面提供了一个纯Java的UUID实现. 不过需要注意的是:这里产生的可能不是真正的UUID，只不过重复的机会少一些而已。<br />import  java.net.InetAddress;<br />import  java.net.UnknownHostException;<br /><br />/** */ /**<br /> * UUID（Univeral Unique Identity）类<br /> * <br /><br /><br /> * Title: 生成唯一编码<br /> * <br /><br /> * <br /><br /><br /> * Description: 源自于w3c.org &lt;&lt;/font&gt;<br />http://源自于w3c.org &gt;<br /> * <br /> * <br /><br /><br /> * Copyright: Copyright (c) 2001-2004<br /> * <br /><br /> * <br /> *  @version  1.0<br />  */<br />public   final   class  UUID  {<br /><br />     /** */ /**<br />     *  @serial  Integer that helps create a unique UID.<br />      */<br />     private   int  unique;<br /><br />     /** */ /**<br />     *  @serial  Long used to record the time. The time will be<br />     *         used to create a unique UID.<br />      */<br />     private   long  time;<br /><br />     /** */ /**<br />     * InetAddress to make the UID globally unique<br />      */<br />     private   static  String address;<br /><br />     /** */ /**<br />     * a random number<br />      */<br />     private   static   int  hostUnique;<br /><br />     /** */ /**<br />     * Used for synchronization<br />      */<br />     private   static  Object mutex;<br /><br />     private   static   long  lastTime;<br /><br />     private   static   long  DELAY;<br /><br />     private   static  String generateNoNetworkID()  {<br />        Thread current  =  Thread.currentThread();<br />        String nid  =  current.activeCount()  +  System.getProperty( " os.version " )<br />                 +  System.getProperty( " user.name  " )<br />                 +  System.getProperty( " java.version " );<br />        System.out.println( " netWorkId = "   +  nid);<br />        MD5 md5  =   new  MD5(nid);<br />        md5.processString();<br />         return  md5.getStringDigest();<br />    }<br /><br />     static   {<br />        hostUnique  =  ( new  Object()).hashCode();<br />        mutex  =   new  Object();<br />        lastTime  =  System.currentTimeMillis();<br />        DELAY  =   10 ;  //  in milliseconds<br />         try   {<br />            String s  =  InetAddress.getLocalHost().getHostAddress();<br />            MD5 md5  =   new  MD5(s);<br />            md5.processString();<br />            address  =  md5.getStringDigest();<br />        }   catch  (UnknownHostException ex)  {<br />            address  =  generateNoNetworkID();<br />        }<br />    }<br /><br />     public  UUID()  {<br />         synchronized  (mutex)  {<br />             boolean  done  =   false ;<br />             while  ( ! done)  {<br />                time  =  System.currentTimeMillis();<br />                 if  (time   lastTime  +  DELAY)  {<br />                     //  pause for a second to wait for time to change<br />                     try   {<br />                        Thread.currentThread().sleep(DELAY);<br />                    }   catch  (java.lang.InterruptedException e)  {<br />                    }   //  ignore exception<br />                     continue ;<br />                }   else   {<br />                    lastTime  =  time;<br />                    done  =   true ;<br />                }<br />            }<br />            unique  =  hostUnique;<br />        }<br />    }<br /><br />     public  String toString()  {<br />         return  Integer.toString(unique,  16 )  +  Long.toString(time,  16 )  +  address;<br />    }<br /><br />     public   boolean  equals(Object obj)  {<br />         if  ((obj  !=   null )  &amp;&amp;  (obj  instanceof  UUID))  {<br />            UUID uuid  =  (UUID) obj;<br />             return  (unique  ==  uuid.unique  &amp;&amp;  time  ==  uuid.time  &amp;&amp;  address<br />                    .equals(uuid.address));<br />        }   else   {<br />             return   false ;<br />        }<br />    }<br /><br />     public   static   void  main(String args[])  {<br />        System.out.println( new  UUID());<br />        System.out.println( new  UUID());<br />        System.out.println( new  UUID());<br />         long  start  =  System.currentTimeMillis();<br />        System.out.println( new  UUID());<br />         long  end  =  System.currentTimeMillis();<br />        System.out.println((end  -  start));<br />        System.out.println( new  UUID().toString().length());<br />    }<br /><br />     /** */ /**<br />     * 返回最新的UUID号码<br />     * <br />     *  @return  String UUID，长50位<br />     * <br />      */<br />     public   final   static  String getUUID()  {<br />        UUID uid  =   new  UUID();<br />         return  uid.toString();<br />    }<br />}<br /><br />其中使用到MD5加密算法,实现代码如下:<br /><br />import  java.io.ByteArrayInputStream;<br />import  java.io.File;<br />import  java.io.FileInputStream;<br />import  java.io.IOException;<br />import  java.io.InputStream;<br />import  java.io.UnsupportedEncodingException;<br /><br />/** */ /**<br /> * MD5 加密算法类<br /> * <br /> * <br /><br /><br /> * Description: 源自于w3c.org &lt;&lt;/font&gt;<br />http://源自于w3c.org &gt;<br /> * <br /> * <br /><br /><br /> * Copyright: Copyright (c) 2001-2004<br /> * <br /><br /> * <br /> *  @version  1.0<br />  */<br />public   class  MD5  {<br />     private   static   final   int  BUFFER_SIZE  =   1024 ;<br /><br />     private   static   final   int  S11  =   7 ;<br /><br />     private   static   final   int  S12  =   12 ;<br /><br />     private   static   final   int  S13  =   17 ;<br /><br />     private   static   final   int  S14  =   22 ;<br /><br />     private   static   final   int  S21  =   5 ;<br /><br />     private   static   final   int  S22  =   9 ;<br /><br />     private   static   final   int  S23  =   14 ;<br /><br />     private   static   final   int  S24  =   20 ;<br /><br />     private   static   final   int  S31  =   4 ;<br /><br />     private   static   final   int  S32  =   11 ;<br /><br />     private   static   final   int  S33  =   16 ;<br /><br />     private   static   final   int  S34  =   23 ;<br /><br />     private   static   final   int  S41  =   6 ;<br /><br />     private   static   final   int  S42  =   10 ;<br /><br />     private   static   final   int  S43  =   15 ;<br /><br />     private   static   final   int  S44  =   21 ;<br /><br />     private   static   byte  padding[]  =   { ( byte )  0x80 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 , ( byte )  0 ,<br />            ( byte )  0  } ;<br /><br />     private  InputStream in  =   null ;<br /><br />     private   boolean  stringp  =   false ;<br /><br />     private   int  state[]  =   null ;<br /><br />     private   long  count  =   0 ;<br /><br />     private   byte  buffer[]  =   null ;<br /><br />     private   byte  digest[]  =   null ;<br /><br />     private   static  String stringify( byte  buf[])  {<br />        StringBuffer sb  =   new  StringBuffer( 2   *  buf.length);<br />         for  ( int  i  =   0 ; i   buf.length; i ++ )  {<br />             int  h  =  (buf[i]  &amp;   0xf0 )  &gt;&gt;   4 ;<br />             int  l  =  (buf[i]  &amp;   0x0f );<br />            sb.append( new  Character(( char ) ((h  &gt;   9 )  ?   ' a '   +  h  -   10  :  ' 0 '   +  h)));<br />            sb.append( new  Character(( char ) ((l  &gt;   9 )  ?   ' a '   +  l  -   10  :  ' 0 '   +  l)));<br />        }<br />         return  sb.toString();<br />    }<br /><br />     private   final   int  F( int  x,  int  y,  int  z)  {<br />         return  ((x  &amp;  y)  |  (( ~ x)  &amp;  z));<br />    }<br /><br />     private   final   int  G( int  x,  int  y,  int  z)  {<br />         return  ((x  &amp;  z)  |  (y  &amp;  ( ~ z)));<br />    }<br /><br />     private   final   int  H( int  x,  int  y,  int  z)  {<br />         return  (x  ^  y  ^  z);<br />    }<br /><br />     private   final   int  I( int  x,  int  y,  int  z)  {<br />         return  (y  ^  (x  |  ( ~ z)));<br />    }<br /><br />     private   final   int  rotate_left( int  x,  int  n)  {<br />         return  ((x  &lt;&lt;&lt;/span&gt;  n) | (x &gt;&gt;&gt; (32 -  n)));<br />    }<br /><br />     private   final   int  FF( int  a,  int  b,  int  c,  int  d,  int  x,  int  s,  int  ac)  {<br />        a  +=  (F(b, c, d)  +  x  +  ac);<br />        a  =  rotate_left(a, s);<br />        a  +=  b;<br />         return  a;<br />    }<br /><br />     private   final   int  GG( int  a,  int  b,  int  c,  int  d,  int  x,  int  s,  int  ac)  {<br />        a  +=  (G(b, c, d)  +  x  +  ac);<br />        a  =  rotate_left(a, s);<br />        a  +=  b;<br />         return  a;<br />    }<br /><br />     private   final   int  HH( int  a,  int  b,  int  c,  int  d,  int  x,  int  s,  int  ac)  {<br />        a  +=  (H(b, c, d)  +  x  +  ac);<br />        a  =  rotate_left(a, s);<br />        a  +=  b;<br />         return  a;<br />    }<br /><br />     private   final   int  II( int  a,  int  b,  int  c,  int  d,  int  x,  int  s,  int  ac)  {<br />        a  +=  (I(b, c, d)  +  x  +  ac);<br />        a  =  rotate_left(a, s);<br />        a  +=  b;<br />         return  a;<br />    }<br /><br />     private   final   void  decode( int  output[],  byte  input[],  int  off,  int  len)  {<br />         int  i  =   0 ;<br />         int  j  =   0 ;<br />         for  (; j   len; i ++ , j  +=   4 )  {<br />            output[i]  =  ((( int ) (input[off  +  j]  &amp;   0xff ))<br />                     |  ((( int ) (input[off  +  j  +   1 ]  &amp;   0xff ))  &lt;&lt;&lt;/span&gt;  8 )<br />                     |  ((( int ) (input[off  +  j  +   2 ]  &amp;   0xff ))  &lt;&lt;&lt;/span&gt;  16) | (((int ) (input[off<br />                     +  j  +   3 ]  &amp;   0xff ))  &lt;&lt;&lt;/span&gt;  24 ));<br />        }<br />    }<br /><br />     private   final   void  transform( byte  block[],  int  offset)  {<br />         int  a  =  state[ 0 ];<br />         int  b  =  state[ 1 ];<br />         int  c  =  state[ 2 ];<br />         int  d  =  state[ 3 ];<br />         int  x[]  =   new   int [ 16 ];<br /><br />        decode(x, block, offset,  64 );<br />         /**/ /*  Round 1  */<br />        a  =  FF(a, b, c, d, x[ 0 ], S11,  0xd76aa478 );  /**/ /*  1  */<br />        d  =  FF(d, a, b, c, x[ 1 ], S12,  0xe8c7b756 );  /**/ /*  2  */<br />        c  =  FF(c, d, a, b, x[ 2 ], S13,  0x242070db );  /**/ /*  3  */<br />        b  =  FF(b, c, d, a, x[ 3 ], S14,  0xc1bdceee );  /**/ /*  4  */<br />        a  =  FF(a, b, c, d, x[ 4 ], S11,  0xf57c0faf );  /**/ /*  5  */<br />        d  =  FF(d, a, b, c, x[ 5 ], S12,  0x4787c62a );  /**/ /*  6  */<br />        c  =  FF(c, d, a, b, x[ 6 ], S13,  0xa8304613 );  /**/ /*  7  */<br />        b  =  FF(b, c, d, a, x[ 7 ], S14,  0xfd469501 );  /**/ /*  8  */<br />        a  =  FF(a, b, c, d, x[ 8 ], S11,  0x698098d8 );  /**/ /*  9  */<br />        d  =  FF(d, a, b, c, x[ 9 ], S12,  0x8b44f7af );  /**/ /*  10  */<br />        c  =  FF(c, d, a, b, x[ 10 ], S13,  0xffff5bb1 );  /**/ /*  11  */<br />        b  =  FF(b, c, d, a, x[ 11 ], S14,  0x895cd7be );  /**/ /*  12  */<br />        a  =  FF(a, b, c, d, x[ 12 ], S11,  0x6b901122 );  /**/ /*  13  */<br />        d  =  FF(d, a, b, c, x[ 13 ], S12,  0xfd987193 );  /**/ /*  14  */<br />        c  =  FF(c, d, a, b, x[ 14 ], S13,  0xa679438e );  /**/ /*  15  */<br />        b  =  FF(b, c, d, a, x[ 15 ], S14,  0x49b40821 );  /**/ /*  16  */<br />         /**/ /*  Round 2  */<br />        a  =  GG(a, b, c, d, x[ 1 ], S21,  0xf61e2562 );  /**/ /*  17  */<br />        d  =  GG(d, a, b, c, x[ 6 ], S22,  0xc040b340 );  /**/ /*  18  */<br />        c  =  GG(c, d, a, b, x[ 11 ], S23,  0x265e5a51 );  /**/ /*  19  */<br />        b  =  GG(b, c, d, a, x[ 0 ], S24,  0xe9b6c7aa );  /**/ /*  20  */<br />        a  =  GG(a, b, c, d, x[ 5 ], S21,  0xd62f105d );  /**/ /*  21  */<br />        d  =  GG(d, a, b, c, x[ 10 ], S22,  0x2441453 );  /**/ /*  22  */<br />        c  =  GG(c, d, a, b, x[ 15 ], S23,  0xd8a1e681 );  /**/ /*  23  */<br />        b  =  GG(b, c, d, a, x[ 4 ], S24,  0xe7d3fbc8 );  /**/ /*  24  */<br />        a  =  GG(a, b, c, d, x[ 9 ], S21,  0x21e1cde6 );  /**/ /*  25  */<br />        d  =  GG(d, a, b, c, x[ 14 ], S22,  0xc33707d6 );  /**/ /*  26  */<br />        c  =  GG(c, d, a, b, x[ 3 ], S23,  0xf4d50d87 );  /**/ /*  27  */<br />        b  =  GG(b, c, d, a, x[ 8 ], S24,  0x455a14ed );  /**/ /*  28  */<br />        a  =  GG(a, b, c, d, x[ 13 ], S21,  0xa9e3e905 );  /**/ /*  29  */<br />        d  =  GG(d, a, b, c, x[ 2 ], S22,  0xfcefa3f8 );  /**/ /*  30  */<br />        c  =  GG(c, d, a, b, x[ 7 ], S23,  0x676f02d9 );  /**/ /*  31  */<br />        b  =  GG(b, c, d, a, x[ 12 ], S24,  0x8d2a4c8a );  /**/ /*  32  */<br /><br />         /**/ /*  Round 3  */<br />        a  =  HH(a, b, c, d, x[ 5 ], S31,  0xfffa3942 );  /**/ /*  33  */<br />        d  =  HH(d, a, b, c, x[ 8 ], S32,  0x8771f681 );  /**/ /*  34  */<br />        c  =  HH(c, d, a, b, x[ 11 ], S33,  0x6d9d6122 );  /**/ /*  35  */<br />        b  =  HH(b, c, d, a, x[ 14 ], S34,  0xfde5380c );  /**/ /*  36  */<br />        a  =  HH(a, b, c, d, x[ 1 ], S31,  0xa4beea44 );  /**/ /*  37  */<br />        d  =  HH(d, a, b, c, x[ 4 ], S32,  0x4bdecfa9 );  /**/ /*  38  */<br />        c  =  HH(c, d, a, b, x[ 7 ], S33,  0xf6bb4b60 );  /**/ /*  39  */<br />        b  =  HH(b, c, d, a, x[ 10 ], S34,  0xbebfbc70 );  /**/ /*  40  */<br />        a  =  HH(a, b, c, d, x[ 13 ], S31,  0x289b7ec6 );  /**/ /*  41  */<br />        d  =  HH(d, a, b, c, x[ 0 ], S32,  0xeaa127fa );  /**/ /*  42  */<br />        c  =  HH(c, d, a, b, x[ 3 ], S33,  0xd4ef3085 );  /**/ /*  43  */<br />        b  =  HH(b, c, d, a, x[ 6 ], S34,  0x4881d05 );  /**/ /*  44  */<br />        a  =  HH(a, b, c, d, x[ 9 ], S31,  0xd9d4d039 );  /**/ /*  45  */<br />        d  =  HH(d, a, b, c, x[ 12 ], S32,  0xe6db99e5 );  /**/ /*  46  */<br />        c  =  HH(c, d, a, b, x[ 15 ], S33,  0x1fa27cf8 );  /**/ /*  47  */<br />        b  =  HH(b, c, d, a, x[ 2 ], S34,  0xc4ac5665 );  /**/ /*  48  */<br /><br />         /**/ /*  Round 4  */<br />        a  =  II(a, b, c, d, x[ 0 ], S41,  0xf4292244 );  /**/ /*  49  */<br />        d  =  II(d, a, b, c, x[ 7 ], S42,  0x432aff97 );  /**/ /*  50  */<br />        c  =  II(c, d, a, b, x[ 14 ], S43,  0xab9423a7 );  /**/ /*  51  */<br />        b  =  II(b, c, d, a, x[ 5 ], S44,  0xfc93a039 );  /**/ /*  52  */<br />        a  =  II(a, b, c, d, x[ 12 ], S41,  0x655b59c3 );  /**/ /*  53  */<br />        d  =  II(d, a, b, c, x[ 3 ], S42,  0x8f0ccc92 );  /**/ /*  54  */<br />        c  =  II(c, d, a, b, x[ 10 ], S43,  0xffeff47d );  /**/ /*  55  */<br />        b  =  II(b, c, d, a, x[ 1 ], S44,  0x85845dd1 );  /**/ /*  56  */<br />        a  =  II(a, b, c, d, x[ 8 ], S41,  0x6fa87e4f );  /**/ /*  57  */<br />        d  =  II(d, a, b, c, x[ 15 ], S42,  0xfe2ce6e0 );  /**/ /*  58  */<br />        c  =  II(c, d, a, b, x[ 6 ], S43,  0xa3014314 );  /**/ /*  59  */<br />        b  =  II(b, c, d, a, x[ 13 ], S44,  0x4e0811a1 );  /**/ /*  60  */<br />        a  =  II(a, b, c, d, x[ 4 ], S41,  0xf7537e82 );  /**/ /*  61  */<br />        d  =  II(d, a, b, c, x[ 11 ], S42,  0xbd3af235 );  /**/ /*  62  */<br />        c  =  II(c, d, a, b, x[ 2 ], S43,  0x2ad7d2bb );  /**/ /*  63  */<br />        b  =  II(b, c, d, a, x[ 9 ], S44,  0xeb86d391 );  /**/ /*  64  */<br /><br />        state[ 0 ]  +=  a;<br />        state[ 1 ]  +=  b;<br />        state[ 2 ]  +=  c;<br />        state[ 3 ]  +=  d;<br />    }<br /><br />     private   final   void  update( byte  input[],  int  len)  {<br />         int  index  =  (( int ) (count  &gt;&gt;   3 ))  &amp;   0x3f ;<br />        count  +=  (len  &lt;&lt;&lt;/span&gt;  3 );<br />         int  partLen  =   64   -  index;<br />         int  i  =   0 ;<br />         if  (len  &gt;=  partLen)  {<br />            System.arraycopy(input,  0 , buffer, index, partLen);<br />            transform(buffer,  0 );<br />             for  (i  =  partLen; i  +   63    len; i  +=   64 )<br />                transform(input, i);<br />            index  =   0 ;<br />        }   else   {<br />            i  =   0 ;<br />        }<br />        System.arraycopy(input, i, buffer, index, len  -  i);<br />    }<br /><br />     private   byte [] end()  {<br />         byte  bits[]  =   new   byte [ 8 ];<br />         for  ( int  i  =   0 ; i    8 ; i ++ )<br />            bits[i]  =  ( byte ) ((count  &gt;&gt;&gt;  (i  *   8 ))  &amp;   0xff );<br />         int  index  =  (( int ) (count  &gt;&gt;   3 ))  &amp;   0x3f ;<br />         int  padlen  =  (index    56 )  ?  ( 56   -  index) : ( 120   -  index);<br />        update(padding, padlen);<br />        update(bits,  8 );<br />         return  encode(state,  16 );<br />    }<br /><br />     //  Encode the content.state array into 16 bytes array<br />     private   byte [] encode( int  input[],  int  len)  {<br />         byte  output[]  =   new   byte [len];<br />         int  i  =   0 ;<br />         int  j  =   0 ;<br />         for  (; j   len; i ++ , j  +=   4 )  {<br />            output[j]  =  ( byte ) ((input[i])  &amp;   0xff );<br />            output[j  +   1 ]  =  ( byte ) ((input[i]  &gt;&gt;   8 )  &amp;   0xff );<br />            output[j  +   2 ]  =  ( byte ) ((input[i]  &gt;&gt;   16 )  &amp;   0xff );<br />            output[j  +   3 ]  =  ( byte ) ((input[i]  &gt;&gt;   24 )  &amp;   0xff );<br />        }<br />         return  output;<br />    }<br /><br />     /** */ /**<br />     * Get the digest for our input stream. This method constructs the input<br />     * stream digest, and return it, as a a String, following the MD5 (rfc1321)<br />     * algorithm,<br />     * <br />     *  @return  An instance of String, giving the message digest.<br />     *  @exception  IOException<br />     *                Thrown if the digestifier was unable to read the input<br />     *                stream.<br />      */<br /><br />     public   byte [] getDigest()  throws  IOException  {<br />         byte  buffer[]  =   new   byte [BUFFER_SIZE];<br />         int  got  =   - 1 ;<br /><br />         if  (digest  !=   null )<br />             return  digest;<br />         while  ((got  =  in.read(buffer))  &gt;   0 )<br />            update(buffer, got);<br />         this .digest  =  end();<br />         return  digest;<br />    }<br /><br />     /** */ /**<br />     * Get the digest, for this string digestifier. This method doesn't throw<br />     * any IOException, since it knows that the underlying stream ws built from<br />     * a String.<br />      */<br /><br />     public   byte [] processString()  {<br />         if  ( ! stringp)<br />             throw   new  RuntimeException( this .getClass().getName()<br />                     +   " [processString] "   +   "  not a string. " );<br />         try   {<br />             return  getDigest();<br />        }   catch  (IOException ex)  {<br />        }<br />         throw   new  RuntimeException( this .getClass().getName()<br />                 +   " [processString] "   +   " : implementation error. " );<br />    }<br /><br />     /** */ /**<br />     * Get the digest, as a proper string.<br />      */<br /><br />     public  String getStringDigest()  {<br />         if  (digest  ==   null )<br />             throw   new  RuntimeException( this .getClass().getName()<br />                     +   " [getStringDigest] "   +   " : called before processing. " );<br />         return  stringify(digest);<br />    }<br /><br />     /** */ /**<br />     * Construct a digestifier for the given string.<br />     * <br />     *  @param  input<br />     *            The string to be digestified.<br />     *  @param  encoding<br />     *            the encoding name used (such as UTF8)<br />      */<br /><br />     public  MD5(String input, String enc)  {<br />         byte  bytes[]  =   null ;<br />         try   {<br />            bytes  =  input.getBytes(enc);<br />        }   catch  (UnsupportedEncodingException e)  {<br />             throw   new  RuntimeException( " no  "   +  enc  +   "  encoding!!! " );<br />        }<br />         this .stringp  =   true ;<br />         this .in  =   new  ByteArrayInputStream(bytes);<br />         this .state  =   new   int [ 4 ];<br />         this .buffer  =   new   byte [ 64 ];<br />         this .count  =   0 ;<br />        state[ 0 ]  =   0x67452301 ;<br />        state[ 1 ]  =   0xefcdab89 ;<br />        state[ 2 ]  =   0x98badcfe ;<br />        state[ 3 ]  =   0x10325476 ;<br />    }<br /><br />     /** */ /**<br />     * Construct a digestifier for the given string.<br />     * <br />     *  @param  input<br />     *            The string to be digestified.<br />      */<br /><br />     public  MD5(String input)  {<br />         this (input,  " UTF8 " );<br />    }<br /><br />     /** */ /**<br />     * Construct a digestifier for the given input stream.<br />     * <br />     *  @param  in<br />     *            The input stream to be digestified.<br />      */<br /><br />     public  MD5(InputStream in)  {<br />         this .stringp  =   false ;<br />         this .in  =  in;<br />         this .state  =   new   int [ 4 ];<br />         this .buffer  =   new   byte [ 64 ];<br />         this .count  =   0 ;<br />        state[ 0 ]  =   0x67452301 ;<br />        state[ 1 ]  =   0xefcdab89 ;<br />        state[ 2 ]  =   0x98badcfe ;<br />        state[ 3 ]  =   0x10325476 ;<br />    }<br /><br />     public   static   void  main(String args[])  throws  IOException  {<br />         if  (args.length  !=   1 )  {<br />            System.out.println( " Md5  " );<br />            System.exit( 1 );<br />        }<br />        MD5 md5  =   new  MD5( new  FileInputStream( new  File(args[ 0 ])));<br />         byte  b[]  =  md5.getDigest();<br />        System.out.println(stringify(b));<br />    }<br /><br />}<br /><img src ="http://www.blogjava.net/lijiajia418/aggbug/65525.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:17 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65525.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个Socket服务器样板程序</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65524.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:13:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65524.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65524.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65524.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65524.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65524.html</trackback:ping><description><![CDATA[这是一个非常好的Socket服务器样板程序，这个socket服务器可以为你建立指定的监听端口、客户端请求响应机制等一些服务器所具备的基本框架<br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre dragover="true"><code dragover="true"><br /><br />/*<br /><br /> * Copyright (c) 2000 David Flanagan.  All rights reserved.<br /><br /> * This code is from the book Java Examples in a Nutshell, 2nd Edition.<br /><br /> * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.<br /><br /> * You may study, use, and modify it for any non-commercial purpose.<br /><br /> * You may distribute it non-commercially as long as you retain this notice.<br /><br /> * For a commercial use license, or to purchase the book (recommended),<br /><br /> * visit http://www.davidflanagan.com/javaexamples2.<br /><br /> */<br /><br /><br /><br />import java.io.*;<br /><br />import java.net.*;<br /><br />import java.util.*;<br /><br /><br /><br />/**<br /><br /> * This class is a generic framework for a flexible, multi-threaded server.<br /><br /> * It listens on any number of specified ports, and, when it receives a <br /><br /> * connection on a port, passes input and output streams to a specified Service<br /><br /> * object which provides the actual service.  It can limit the number of<br /><br /> * concurrent connections, and logs activity to a specified stream.<br /><br /> **/<br /><br />public class Server {<br /><br />    /**<br /><br />     * A main() method for running the server as a standalone program.  The<br /><br />     * command-line arguments to the program should be pairs of servicenames<br /><br />     * and port numbers.  For each pair, the program will dynamically load the<br /><br />     * named Service class, instantiate it, and tell the server to provide<br /><br />     * that Service on the specified port.  The special -control argument<br /><br />     * should be followed by a password and port, and will start special<br /><br />     * server control service running on the specified port, protected by the<br /><br />     * specified password.<br /><br />     **/<br /><br />    public static void main(String[] args) {<br /><br />        try {<br /><br />            if (args.length &lt; 2)  // Check number of arguments<br /><br />                throw new IllegalArgumentException("Must specify a service");<br /><br /><br /><br />            // Create a Server object that uses standard out as its log and<br /><br />            // has a limit of ten concurrent connections at once.<br /><br />            Server s = new Server(System.out, 10);<br /><br /><br /><br />            // Parse the argument list<br /><br />            int i = 0;<br /><br />            while(i &lt; args.length) {<br /><br />                if (args[i].equals("-control")) {  // Handle the -control arg<br /><br />                    i++;<br /><br />                    String password = args[i++];<br /><br />                    int port = Integer.parseInt(args[i++]);<br /><br />      // add control service<br /><br />                    s.addService(new Control(s, password), port);<br /><br />                } <br /><br />                else {<br /><br />                    // Otherwise start a named service on the specified port.<br /><br />                    // Dynamically load and instantiate a Service class<br /><br />                    String serviceName = args[i++];<br /><br />                    Class serviceClass = Class.forName(serviceName); <br /><br />                    Service service = (Service)serviceClass.newInstance();<br /><br />                    int port = Integer.parseInt(args[i++]);<br /><br />                    s.addService(service, port);<br /><br />                }<br /><br />            }<br /><br />        }<br /><br />        catch (Exception e) { // Display a message if anything goes wrong<br /><br />            System.err.println("Server: " + e);<br /><br />            System.err.println("Usage: java Server " +<br /><br />          "[-control <password><port>] " +<br /><br />          "[<servicename><port> ... ]");<br /><br />            System.exit(1);<br /><br />        }<br /><br />    }<br /><br /><br /><br />    // This is the state for the server<br /><br />    Map services;                   // Hashtable mapping ports to Listeners<br /><br />    Set connections;                // The set of current connections<br /><br />    int maxConnections;             // The concurrent connection limit<br /><br />    ThreadGroup threadGroup;        // The threadgroup for all our threads<br /><br />    PrintWriter logStream;          // Where we send our logging output to<br /><br /><br /><br />    /**<br /><br />     * This is the Server() constructor.  It must be passed a stream <br /><br />     * to send log output to (may be null), and the limit on the number of<br /><br />     * concurrent connections.  <br /><br />     **/<br /><br />    public Server(OutputStream logStream, int maxConnections) { <br /><br />        setLogStream(logStream);<br /><br />        log("Starting server");<br /><br />        threadGroup = new ThreadGroup(Server.class.getName());<br /><br /> this.maxConnections = maxConnections;<br /><br />        services = new HashMap();<br /><br /> connections = new HashSet(maxConnections);<br /><br />    }<br /><br /><br /><br />    /** <br /><br />     * A public method to set the current logging stream.  Pass null<br /><br />     * to turn logging off<br /><br />     **/<br /><br />    public synchronized void setLogStream(OutputStream out) {<br /><br />        if (out != null) logStream = new PrintWriter(out);<br /><br />        else logStream = null;<br /><br />    }<br /><br /><br /><br />    /** Write the specified string to the log */<br /><br />    protected synchronized void log(String s) { <br /><br />        if (logStream != null) {<br /><br />            logStream.println("[" + new Date() + "] " + s);<br /><br />            logStream.flush();<br /><br />        }<br /><br />    }<br /><br />    /** Write the specified object to the log */<br /><br />    protected void log(Object o) { log(o.toString()); }<br /><br /><br /><br />    /**<br /><br />     * This method makes the server start providing a new service.<br /><br />     * It runs the specified Service object on the specified port.<br /><br />     **/<br /><br />    public synchronized void addService(Service service, int port)<br /><br /> throws IOException<br /><br />    {<br /><br />        Integer key = new Integer(port);  // the hashtable key<br /><br />        // Check whether a service is already on that port<br /><br />        if (services.get(key) != null) <br /><br />            throw new IllegalArgumentException("Port " + port +<br /><br />            " already in use.");<br /><br />        // Create a Listener object to listen for connections on the port<br /><br />        Listener listener = new Listener(threadGroup, port, service);<br /><br />        // Store it in the hashtable<br /><br />        services.put(key, listener);<br /><br />        // Log it<br /><br />        log("Starting service " + service.getClass().getName() + <br /><br />     " on port " + port);<br /><br />        // Start the listener running.<br /><br />        listener.start();<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This method makes the server stop providing a service on a port.<br /><br />     * It does not terminate any pending connections to that service, merely<br /><br />     * causes the server to stop accepting new connections<br /><br />     **/<br /><br />    public synchronized void removeService(int port) {<br /><br />        Integer key = new Integer(port);  // hashtable key<br /><br />        // Look up the Listener object for the port in the hashtable<br /><br />        final Listener listener = (Listener) services.get(key);<br /><br />        if (listener == null) return;<br /><br />        // Ask the listener to stop<br /><br />        listener.pleaseStop();<br /><br />        // Remove it from the hashtable<br /><br />        services.remove(key);<br /><br />        // And log it.<br /><br />        log("Stopping service " + listener.service.getClass().getName() + <br /><br />     " on port " + port);<br /><br />    }<br /><br /><br /><br />    /** <br /><br />     * This nested Thread subclass is a "listener".  It listens for<br /><br />     * connections on a specified port (using a ServerSocket) and when it gets<br /><br />     * a connection request, it calls the servers addConnection() method to<br /><br />     * accept (or reject) the connection.  There is one Listener for each<br /><br />     * Service being provided by the Server.<br /><br />     **/<br /><br />    public class Listener extends Thread {<br /><br />        ServerSocket listen_socket;    // The socket to listen for connections<br /><br />        int port;                      // The port we're listening on<br /><br />        Service service;               // The service to provide on that port<br /><br />        volatile boolean stop = false; // Whether we've been asked to stop<br /><br /><br /><br />        /**<br /><br />  * The Listener constructor creates a thread for itself in the<br /><br />  * threadgroup.  It creates a ServerSocket to listen for connections<br /><br />  * on the specified port.  It arranges for the ServerSocket to be<br /><br />  * interruptible, so that services can be removed from the server.<br /><br />  **/<br /><br />        public Listener(ThreadGroup group, int port, Service service) <br /><br />     throws IOException<br /><br /> {<br /><br />            super(group, "Listener:" + port);      <br /><br />            listen_socket = new ServerSocket(port);<br /><br />            // give it a non-zero timeout so accept() can be interrupted<br /><br />            listen_socket.setSoTimeout(600000);<br /><br />            this.port = port;<br /><br />            this.service = service;<br /><br />        }<br /><br /><br /><br />        /** <br /><br />  * This is the polite way to get a Listener to stop accepting<br /><br />  * connections<br /><br />  ***/<br /><br />        public void pleaseStop() {<br /><br />            this.stop = true;              // Set the stop flag<br /><br />            this.interrupt();              // Stop blocking in accept()<br /><br />     try { listen_socket.close(); } // Stop listening.<br /><br />     catch(IOException e) {}<br /><br />        }<br /><br /><br /><br />        /**<br /><br />  * A Listener is a Thread, and this is its body.<br /><br />  * Wait for connection requests, accept them, and pass the socket on<br /><br />  * to the addConnection method of the server.<br /><br />  **/<br /><br />        public void run() {<br /><br />            while(!stop) {      // loop until we're asked to stop.<br /><br />                try {<br /><br />                    Socket client = listen_socket.accept();<br /><br />                    addConnection(client, service);<br /><br />                } <br /><br />                catch (InterruptedIOException e) {} <br /><br />                catch (IOException e) {log(e);}<br /><br />            }<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This is the method that Listener objects call when they accept a<br /><br />     * connection from a client.  It either creates a Connection object <br /><br />     * for the connection and adds it to the list of current connections,<br /><br />     * or, if the limit on connections has been reached, it closes the <br /><br />     * connection. <br /><br />     **/<br /><br />    protected synchronized void addConnection(Socket s, Service service) {<br /><br /> // If the connection limit has been reached<br /><br /> if (connections.size() &gt;= maxConnections) {<br /><br />     try {<br /><br />  // Then tell the client it is being rejected.<br /><br />  PrintWriter out = new PrintWriter(s.getOutputStream());<br /><br />  out.print("Connection refused; " +<br /><br />     "the server is busy; please try again later.\n");<br /><br />  out.flush();<br /><br />  // And close the connection to the rejected client.<br /><br />  s.close();<br /><br />  // And log it, of course<br /><br />  log("Connection refused to " +<br /><br />      s.getInetAddress().getHostAddress() +<br /><br />      ":" + s.getPort() + ": max connections reached.");<br /><br />     } catch (IOException e) {log(e);}<br /><br /> }<br /><br /> else {  // Otherwise, if the limit has not been reached<br /><br />     // Create a Connection thread to handle this connection<br /><br />     Connection c = new Connection(s, service);<br /><br />     // Add it to the list of current connections<br /><br />     connections.add(c);<br /><br />     // Log this new connection<br /><br />     log("Connected to " + s.getInetAddress().getHostAddress() +<br /><br />  ":" + s.getPort() + " on port " + s.getLocalPort() +<br /><br />  " for service " + service.getClass().getName());<br /><br />     // And start the Connection thread to provide the service<br /><br />     c.start();<br /><br /> }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * A Connection thread calls this method just before it exits.  It removes<br /><br />     * the specified Connection from the set of connections.<br /><br />     **/<br /><br />    protected synchronized void endConnection(Connection c) {<br /><br /> connections.remove(c);<br /><br /> log("Connection to " + c.client.getInetAddress().getHostAddress() +<br /><br />     ":" + c.client.getPort() + " closed.");<br /><br />    }<br /><br /><br /><br />    /** Change the current connection limit */<br /><br />    public synchronized void setMaxConnections(int max) {<br /><br /> maxConnections = max;<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This method displays status information about the server on the<br /><br />     * specified stream.  It can be used for debugging, and is used by the<br /><br />     * Control service later in this example.<br /><br />     **/<br /><br />    public synchronized void displayStatus(PrintWriter out) {<br /><br /> // Display a list of all Services that are being provided<br /><br /> Iterator keys = services.keySet().iterator();<br /><br /> while(keys.hasNext()) {<br /><br />     Integer port = (Integer) keys.next();<br /><br />     Listener listener = (Listener) services.get(port);<br /><br />     out.print("SERVICE " + listener.service.getClass().getName()<br /><br />        + " ON PORT " + port + "\n");<br /><br /> }<br /><br /><br /><br /> // Display the current connection limit<br /><br /> out.print("MAX CONNECTIONS: " + maxConnections + "\n");<br /><br /><br /><br /> // Display a list of all current connections<br /><br /> Iterator conns = connections.iterator();<br /><br /> while(conns.hasNext()) {<br /><br />     Connection c = (Connection)conns.next();<br /><br />     out.print("CONNECTED TO " +<br /><br />        c.client.getInetAddress().getHostAddress() +<br /><br />        ":" + c.client.getPort() + " ON PORT " +<br /><br />        c.client.getLocalPort() + " FOR SERVICE " +<br /><br />        c.service.getClass().getName() + "\n");<br /><br /> }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This class is a subclass of Thread that handles an individual<br /><br />     * connection between a client and a Service provided by this server.<br /><br />     * Because each such connection has a thread of its own, each Service can<br /><br />     * have multiple connections pending at once.  Despite all the other<br /><br />     * threads in use, this is the key feature that makes this a<br /><br />     * multi-threaded server implementation.<br /><br />     **/<br /><br />    public class Connection extends Thread {<br /><br />        Socket client;     // The socket to talk to the client through<br /><br />        Service service;   // The service being provided to that client<br /><br /><br /><br />        /**<br /><br />  * This constructor just saves some state and calls the superclass<br /><br />  * constructor to create a thread to handle the connection.  Connection<br /><br />  * objects are created by Listener threads.  These threads are part of<br /><br />  * the server's ThreadGroup, so all Connection threads are part of that<br /><br />  * group, too.<br /><br />  **/<br /><br />        public Connection(Socket client, Service service) {<br /><br />            super("Server.Connection:" +<br /><br />    client.getInetAddress().getHostAddress() +<br /><br />    ":" + client.getPort());<br /><br />            this.client = client;<br /><br />            this.service = service;<br /><br />        }<br /><br /><br /><br />        /**<br /><br />  * This is the body of each and every Connection thread.<br /><br />  * All it does is pass the client input and output streams to the<br /><br />  * serve() method of the specified Service object.  That method is<br /><br />  * responsible for reading from and writing to those streams to<br /><br />  * provide the actual service.  Recall that the Service object has<br /><br />  * been passed from the Server.addService() method to a Listener<br /><br />  * object to the addConnection() method to this Connection object, and<br /><br />  * is now finally being used to provide the service.  Note that just<br /><br />  * before this thread exits it always calls the endConnection() method<br /><br />  * to remove itself from the set of connections<br /><br />  **/<br /><br />        public void run() {<br /><br />            try { <br /><br />                InputStream in = client.getInputStream();<br /><br />                OutputStream out = client.getOutputStream();<br /><br />                service.serve(in, out);<br /><br />            } <br /><br />            catch (IOException e) {log(e);}<br /><br />            finally { endConnection(this); }<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * Here is the Service interface that we have seen so much of.  It defines<br /><br />     * only a single method which is invoked to provide the service.  serve()<br /><br />     * will be passed an input stream and an output stream to the client.  It<br /><br />     * should do whatever it wants with them, and should close them before<br /><br />     * returning.<br /><br />     *<br /><br />     * All connections through the same port to this service share a single<br /><br />     * Service object.  Thus, any state local to an individual connection must<br /><br />     * be stored in local variables within the serve() method.  State that<br /><br />     * should be global to all connections on the same port should be stored<br /><br />     * in instance variables of the Service class.  If the same Service is<br /><br />     * running on more than one port, there will typically be different<br /><br />     * Service instances for each port.  Data that should be global to all<br /><br />     * connections on any port should be stored in static variables.<br /><br />     *<br /><br />     * Note that implementations of this interface must have a no-argument <br /><br />     * constructor if they are to be dynamically instantiated by the main()<br /><br />     * method of the Server class.<br /><br />     **/<br /><br />    public interface Service {<br /><br />        public void serve(InputStream in, OutputStream out) throws IOException;<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * A very simple service.  It displays the current time on the server<br /><br />     * to the client, and closes the connection.<br /><br />     **/<br /><br />    public static class Time implements Service {<br /><br />        public void serve(InputStream i, OutputStream o) throws IOException {<br /><br />            PrintWriter out = new PrintWriter(o);<br /><br />            out.print(new Date() + "\n");<br /><br />            out.close();<br /><br />            i.close();<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This is another example service.  It reads lines of input from the<br /><br />     * client, and sends them back, reversed.  It also displays a welcome<br /><br />     * message and instructions, and closes the connection when the user <br /><br />     * enters a '.' on a line by itself.<br /><br />     **/<br /><br />    public static class Reverse implements Service {<br /><br />        public void serve(InputStream i, OutputStream o) throws IOException {<br /><br />            BufferedReader in = new BufferedReader(new InputStreamReader(i));<br /><br />            PrintWriter out = <br /><br />                new PrintWriter(new BufferedWriter(new OutputStreamWriter(o)));<br /><br />            out.print("Welcome to the line reversal server.\n");<br /><br />            out.print("Enter lines.  End with a '.' on a line by itself.\n");<br /><br />            for(;;) {<br /><br />                out.print("&gt; ");<br /><br />                out.flush();<br /><br />                String line = in.readLine();<br /><br />                if ((line == null) || line.equals(".")) break;<br /><br />                for(int j = line.length()-1; j &gt;= 0; j--)<br /><br />                    out.print(line.charAt(j));<br /><br />                out.print("\n");<br /><br />            }<br /><br />            out.close();<br /><br />            in.close();<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This service is an HTTP mirror, just like the HttpMirror class<br /><br />     * implemented earlier in this chapter.  It echos back the client's<br /><br />     * HTTP request<br /><br />     **/<br /><br />    public static class HTTPMirror implements Service {<br /><br />        public void serve(InputStream i, OutputStream o) throws IOException {<br /><br />            BufferedReader in = new BufferedReader(new InputStreamReader(i));<br /><br />            PrintWriter out = new PrintWriter(o);<br /><br />            out.print("HTTP/1.0 200 \n");<br /><br />            out.print("Content-Type: text/plain\n\n");<br /><br />            String line;<br /><br />            while((line = in.readLine()) != null) {<br /><br />                if (line.length() == 0) break;<br /><br />                out.print(line + "\n");<br /><br />            }<br /><br />            out.close();<br /><br />            in.close();<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This service demonstrates how to maintain state across connections by<br /><br />     * saving it in instance variables and using synchronized access to those<br /><br />     * variables.  It maintains a count of how many clients have connected and<br /><br />     * tells each client what number it is<br /><br />     **/<br /><br />    public static class UniqueID implements Service {<br /><br />        public int id=0;<br /><br />        public synchronized int nextId() { return id++; }<br /><br />        public void serve(InputStream i, OutputStream o) throws IOException {<br /><br />            PrintWriter out = new PrintWriter(o);<br /><br />            out.print("You are client #: " + nextId() + "\n");<br /><br />            out.close();<br /><br />            i.close();<br /><br />        }<br /><br />    }<br /><br /><br /><br />    /**<br /><br />     * This is a non-trivial service.  It implements a command-based protocol<br /><br />     * that gives password-protected runtime control over the operation of the <br /><br />     * server.  See the main() method of the Server class to see how this<br /><br />     * service is started.  <br /><br />     *<br /><br />     * The recognized commands are:<br /><br />     *   password: give password; authorization is required for most commands<br /><br />     *   add:      dynamically add a named service on a specified port<br /><br />     *   remove:   dynamically remove the service running on a specified port<br /><br />     *   max:      change the current maximum connection limit.<br /><br />     *   status:   display current services, connections, and connection limit<br /><br />     *   help:     display a help message<br /><br />     *   quit:     disconnect<br /><br />     *<br /><br />     * This service displays a prompt, and sends all of its output to the user<br /><br />     * in capital letters.  Only one client is allowed to connect to this <br /><br />     * service at a time.<br /><br />     **/<br /><br />    public static class Control implements Service {<br /><br />        Server server;             // The server we control<br /><br />        String password;           // The password we require<br /><br />        boolean connected = false; // Whether a client is already connected<br /><br /><br /><br />        /**<br /><br />  * Create a new Control service.  It will control the specified Server<br /><br />  * object, and will require the specified password for authorization<br /><br />  * Note that this Service does not have a no argument constructor,<br /><br />  * which means that it cannot be dynamically instantiated and added as<br /><br />  * the other, generic services above can be.<br /><br />  **/<br /><br />        public Control(Server server, String password) {<br /><br />            this.server = server;<br /><br />            this.password = password;<br /><br />        }<br /><br /><br /><br />        /**<br /><br />  * This is the serve method that provides the service.  It reads a<br /><br />  * line the client, and uses java.util.StringTokenizer to parse it<br /><br />  * into commands and arguments.  It does various things depending on<br /><br />  * the command.<br /><br />  **/<br /><br />        public void serve(InputStream i, OutputStream o) throws IOException {<br /><br />            // Setup the streams<br /><br />            BufferedReader in = new BufferedReader(new InputStreamReader(i));<br /><br />            PrintWriter out = new PrintWriter(o);<br /><br />            String line;  // For reading client input lines<br /><br />     // Has the user has given the password yet?<br /><br />            boolean authorized = false; <br /><br /><br /><br />            // If there is already a client connected to this service, display<br /><br />            // a message to this client and close the connection.  We use a<br /><br />            // synchronized block to prevent a race condition.<br /><br />            synchronized(this) { <br /><br />                if (connected) { <br /><br />                    out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n");<br /><br />                    out.close();<br /><br />                    return;<br /><br />                }<br /><br />                else connected = true;<br /><br />            }<br /><br /><br /><br />     // This is the main loop: read a command, parse it, and handle it<br /><br />            for(;;) {  // infinite loop<br /><br />                out.print("&gt; ");           // Display a prompt<br /><br />                out.flush();               // Make it appear right away<br /><br />                line = in.readLine();      // Get the user's input<br /><br />                if (line == null) break;   // Quit if we get EOF.<br /><br />                try {<br /><br />                    // Use a StringTokenizer to parse the user's command<br /><br />                    StringTokenizer t = new StringTokenizer(line);<br /><br />                    if (!t.hasMoreTokens()) continue;  // if input was empty<br /><br />                    // Get first word of the input and convert to lower case<br /><br />                    String command = t.nextToken().toLowerCase(); <br /><br />                    // Now compare to each of the possible commands, doing the<br /><br />                    // appropriate thing for each command<br /><br />                    if (command.equals("password")) {  // Password command<br /><br />                        String p = t.nextToken();      // Get the next word<br /><br />                        if (p.equals(this.password)) { // Is it the password?<br /><br />                            out.print("OK\n");         // Say so<br /><br />                            authorized = true;         // Grant authorization<br /><br />                        }<br /><br />                        else out.print("INVALID PASSWORD\n"); // Otherwise fail<br /><br />                    }<br /><br />                    else if (command.equals("add")) {  // Add Service command<br /><br />                        // Check whether password has been given<br /><br />                        if (!authorized) out.print("PASSWORD REQUIRED\n"); <br /><br />                        else {<br /><br />                            // Get the name of the service and try to<br /><br />                            // dynamically load and instantiate it.<br /><br />                            // Exceptions will be handled below<br /><br />                            String serviceName = t.nextToken();<br /><br />                            Class serviceClass = Class.forName(serviceName);<br /><br />                            Service service;<br /><br />                            try {<br /><br />    service = (Service)serviceClass.newInstance();<br /><br />       }<br /><br />                            catch (NoSuchMethodError e) {<br /><br />                                throw new IllegalArgumentException(<br /><br />             "Service must have a " +<br /><br />      "no-argument constructor");<br /><br />                            }<br /><br />                            int port = Integer.parseInt(t.nextToken());<br /><br />                            // If no exceptions occurred, add the service<br /><br />                            server.addService(service, port);<br /><br />                            out.print("SERVICE ADDED\n");    // acknowledge<br /><br />                        }<br /><br />                    }<br /><br />                    else if (command.equals("remove")) { // Remove service<br /><br />                        if (!authorized) out.print("PASSWORD REQUIRED\n");<br /><br />                        else {<br /><br />                            int port = Integer.parseInt(t.nextToken()); <br /><br />                            server.removeService(port); // remove the service<br /><br />                            out.print("SERVICE REMOVED\n"); // acknowledge<br /><br />                        }<br /><br />                    }<br /><br />                    else if (command.equals("max")) { // Set connection limit<br /><br />                        if (!authorized) out.print("PASSWORD REQUIRED\n");<br /><br />                        else {<br /><br />                            int max = Integer.parseInt(t.nextToken()); <br /><br />                            server.setMaxConnections(max); <br /><br />                            out.print("MAX CONNECTIONS CHANGED\n");<br /><br />                        }<br /><br />                    }<br /><br />                    else if (command.equals("status")) { // Status Display<br /><br />                        if (!authorized) out.print("PASSWORD REQUIRED\n");<br /><br />   else server.displayStatus(out);<br /><br />                    }<br /><br />                    else if (command.equals("help")) {  // Help command<br /><br />                        // Display command syntax.  Password not required<br /><br />                        out.print("COMMANDS:\n" + <br /><br />      "\tpassword <password>\n" +<br /><br />      "\tadd <service><port>\n" +<br /><br />      "\tremove <port>\n" +<br /><br />      "\tmax <max-connections>\n" +<br /><br />      "\tstatus\n" +<br /><br />      "\thelp\n" + <br /><br />      "\tquit\n");<br /><br />                    }<br /><br />                    else if (command.equals("quit")) break; // Quit command.<br /><br />                    else out.print("UNRECOGNIZED COMMAND\n"); // Error<br /><br />  }<br /><br />                catch (Exception e) {<br /><br />                    // If an exception occurred during the command, print an<br /><br />                    // error message, then output details of the exception.<br /><br />                    out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" +<br /><br />         e + "\n");<br /><br />                }<br /><br />            }<br /><br />            // Finally, when the loop command loop ends, close the streams<br /><br />            // and set our connected flag to false so that other clients can<br /><br />            // now connect.<br /><br />            connected = false;<br /><br />            out.close();<br /><br />            in.close();<br /><br />        }    <br /><br />    }<br /><br />}<br /></max-connections></port></port></service></password></port></servicename></port></password></code></pre></td></tr></tbody></table><img src ="http://www.blogjava.net/lijiajia418/aggbug/65524.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:13 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65524.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>For-Each 循环</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65021.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 03:20:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65021.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65021.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65021.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65021.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65021.html</trackback:ping><description><![CDATA[
		<h2 style="MARGIN: 13pt 0cm">
				<font face="楷体_GB2312">
						<span lang="EN-US">
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<font size="3">
												<font color="#000000">
														<span style="FONT-FAMILY: 宋体">管中窥虎</span>
														<span lang="EN-US">
																<?XML:NAMESPACE PREFIX = O /?>
																<o:p>
																</o:p>
														</span>
												</font>
										</font>
								</p>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<font size="3">
												<font color="#000000">
														<span style="FONT-FAMILY: 宋体">在学习</span>
														<span lang="EN-US">java 1.5</span>
														<span style="FONT-FAMILY: 宋体">的过程中，我使用了</span>
														<span lang="EN-US">sun</span>
														<span style="FONT-FAMILY: 宋体">公布的</span>
														<span lang="EN-US">tutorial</span>
														<span style="FONT-FAMILY: 宋体">，这份文档写的比较详尽易明，但是对于想快速了解</span>
														<span lang="EN-US">tiger</span>
														<span style="FONT-FAMILY: 宋体">而且具有较好</span>
														<span lang="EN-US">java</span>
														<span style="FONT-FAMILY: 宋体">基础的人来说，大篇幅的英文文档是比较耗时间和非必需的，所以我将会归纳这份文档的主要内容，在保证理解的底线上，尽力减少阅读者需要的时间。</span>
														<span lang="EN-US">
																<o:p>
																</o:p>
														</span>
												</font>
										</font>
								</p>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<span lang="EN-US">
												<font size="3">
														<font color="#000000">
																<o:p> </o:p>
														</font>
												</font>
										</span>
								</p>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<font size="3">
												<font color="#000000">
														<span style="FONT-FAMILY: 宋体">在以下地址可以进入各新增语言特色介绍以及下载相关文档（若有）。</span>
														<span lang="EN-US">
																<o:p>
																</o:p>
														</span>
												</font>
										</font>
								</p>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<span lang="EN-US">
												<a href="http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html">
														<font color="#000000">
																<font size="3">http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html</font>
														</font>
												</a>
												<font size="3">
														<o:p>
														</o:p>
												</font>
										</span>
								</p>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
										<span lang="EN-US">
												<font size="3">
														<o:p> </o:p>
												</font>
										</span>
								</p>
						</span>
						<h1 style="MARGIN: 17pt 0cm 16.5pt">
						</h1>
						<span style="FONT-FAMILY: 楷体_GB2312">
								<font color="#000000">
										<font size="4">
												<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
														<span style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体">第二道虎纹：</span>
														<span lang="EN-US" style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Arial">For-Each </span>
														<span style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 楷体_GB2312">循环</span>
														<span lang="EN-US" style="FONT-SIZE: 14pt">
																<o:p>
																</o:p>
														</span>
												</p>
												<span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 楷体_GB2312">
												</span>
										</font>
								</font>
						</span>
				</font>
		</h2>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">目前在一个容器里做迭代访问挺郁闷的，看看下面这个方法，方法的目的是把容器里的一系列计时任务取消。</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
						<o:p>
						</o:p>
				</span>
		</p>
		<span style="FONT-FAMILY: 宋体">
				<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
						<a href="/Images/OutliningIndicators/ExpandedBlockStart.gif" target="_blank">
								<img id="Codehighlighter1_40_134_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_40_134_Open_Text.style.display='none'; Codehighlighter1_40_134_Closed_Image.style.display='inline'; Codehighlighter1_40_134_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<a href="/Images/OutliningIndicators/ContractedBlock.gif" target="_blank">
								<img id="Codehighlighter1_40_134_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_40_134_Closed_Text.style.display='none'; Codehighlighter1_40_134_Open_Image.style.display='inline'; Codehighlighter1_40_134_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<span style="COLOR: rgb(0,0,255)">void</span>
						<span style="COLOR: rgb(0,0,0)"> cancelAll(Collection</span>
						<span style="COLOR: rgb(0,0,0)">&lt;</span>
						<span style="COLOR: rgb(0,0,0)">TimerTask</span>
						<span style="COLOR: rgb(0,0,0)">&gt;</span>
						<span style="COLOR: rgb(0,0,0)"> c) </span>
						<span id="Codehighlighter1_40_134_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
								<a href="/Images/dot.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
								</a>
						</span>
						<span id="Codehighlighter1_40_134_Open_Text">
								<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>    </span>
								<span style="COLOR: rgb(0,0,255)">for</span>
								<span style="COLOR: rgb(0,0,0)"> (Iterator</span>
								<span style="COLOR: rgb(0,0,0)">&lt;</span>
								<span style="COLOR: rgb(0,0,0)">TimerTask</span>
								<span style="COLOR: rgb(0,0,0)">&gt;</span>
								<span style="COLOR: rgb(0,0,0)"> i </span>
								<span style="COLOR: rgb(0,0,0)">=</span>
								<span style="COLOR: rgb(0,0,0)"> c.iterator(); i.hasNext(); )<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>        i.next().cancel();<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>}</span>
						</span>
						<span style="COLOR: rgb(0,0,0)">
								<br />
								<a href="/Images/OutliningIndicators/None.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
								</a>
						</span>
				</div>
				<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">关于</p>
		</span>
		<font face="宋体">
				<span lang="EN-US">Iterator</span>
				<span style="FONT-FAMILY: 宋体">的部分真的很罗嗦，而且容易出错。现在再看看</span>
				<span lang="EN-US">1.5</span>
				<span style="FONT-FAMILY: 宋体">里带来的</span>
				<span lang="EN-US">For-each</span>
				<span style="FONT-FAMILY: 宋体">循环：</span>
		</font>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
						<o:p>
						</o:p>
				</span>
		</p>
		<span style="FONT-FAMILY: 宋体">
				<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
						<span style="COLOR: rgb(0,0,255)">void</span>
						<span style="COLOR: rgb(0,0,0)"> cancelAll(Collection</span>
						<span style="COLOR: rgb(0,0,0)">&lt;</span>
						<span style="COLOR: rgb(0,0,0)">TimerTask</span>
						<span style="COLOR: rgb(0,0,0)">&gt;</span>
						<span style="COLOR: rgb(0,0,0)"> c) {<br /><br />    </span>
						<span style="COLOR: rgb(0,0,255)">for</span>
						<span style="COLOR: rgb(0,0,0)"> (TimerTask t : c)<br /><br />        t.cancel();<br /><br />}<br /></span>
				</div>
				<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">这个新的循环和泛型完美配合，既保持类型安全，又去掉了冗余。</p>
		</span>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">以下是一个在试图嵌套迭代的时候经常会犯的错误。</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
						<o:p>
						</o:p>
				</span>
		</p>
		<span lang="EN-US">
				<o:p>
						<font face="宋体">
								<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
										<span style="COLOR: rgb(0,0,0)">List suits </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> <a href="/Images/dot.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" /></a>;<br /><br />List ranks </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> <a href="/Images/dot.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" /></a>;<br /><br />List sortedDeck </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> </span>
										<span style="COLOR: rgb(0,0,255)">new</span>
										<span style="COLOR: rgb(0,0,0)"> ArrayList();<br /><br /> <br /><br /></span>
										<span style="COLOR: rgb(0,128,0)">//</span>
										<span style="COLOR: rgb(0,128,0)"> BROKEN - throws NoSuchElementException!</span>
										<span style="COLOR: rgb(0,128,0)">
												<br />
										</span>
										<span style="COLOR: rgb(0,0,0)">
												<br />
										</span>
										<span style="COLOR: rgb(0,0,255)">for</span>
										<span style="COLOR: rgb(0,0,0)"> (Iterator i </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> suits.iterator(); i.hasNext(); )<br /><br />    </span>
										<span style="COLOR: rgb(0,0,255)">for</span>
										<span style="COLOR: rgb(0,0,0)"> (Iterator j </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> ranks.iterator(); j.hasNext(); )<br /><br />        sortedDeck.add(</span>
										<span style="COLOR: rgb(0,0,255)">new</span>
										<span style="COLOR: rgb(0,0,0)"> Card(i.next(), j.next()));<br /></span>
								</div>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left"> </p>
						</font>
				</o:p>
		</span>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">原因是</span>
				<font face="宋体">
						<span lang="EN-US">i.next()</span>
						<span style="FONT-FAMILY: 宋体">被过多的调用了。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">再看看新循环的表现，简直是度身定造一样的般配。</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
						<o:p>
						</o:p>
				</span>
		</p>
		<span lang="EN-US">
				<o:p>
						<font face="宋体">
								<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
										<span style="COLOR: rgb(0,0,255)">for</span>
										<span style="COLOR: rgb(0,0,0)"> (Suit suit : suits)<br /><br />    </span>
										<span style="COLOR: rgb(0,0,255)">for</span>
										<span style="COLOR: rgb(0,0,0)"> (Rank rank : ranks)<br /><br />        sortedDeck.add(</span>
										<span style="COLOR: rgb(0,0,255)">new</span>
										<span style="COLOR: rgb(0,0,0)"> Card(suit, rank));<br /></span>
								</div>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left"> </p>
						</font>
				</o:p>
		</span>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font face="宋体">
						<span lang="EN-US">for-each</span>
						<span style="FONT-FAMILY: 宋体">循环也适用于数组，象隐藏迭代子一样，这次它把数组下标藏起来了。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
						<o:p>
						</o:p>
				</span>
		</p>
		<span lang="EN-US">
				<o:p>
						<font face="宋体">
								<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
										<a href="/Images/OutliningIndicators/None.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<span style="COLOR: rgb(0,128,0)">//</span>
										<span style="COLOR: rgb(0,128,0)"> Returns the sum of the elements of a</span>
										<span style="COLOR: rgb(0,128,0)">
												<br />
												<a href="/Images/OutliningIndicators/None.gif" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
												</a>
										</span>
										<span style="COLOR: rgb(0,0,0)">
												<br />
												<a href="/Images/OutliningIndicators/ExpandedBlockStart.gif" target="_blank">
														<img id="Codehighlighter1_58_145_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_58_145_Open_Text.style.display='none'; Codehighlighter1_58_145_Closed_Image.style.display='inline'; Codehighlighter1_58_145_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
												</a>
												<a href="/Images/OutliningIndicators/ContractedBlock.gif" target="_blank">
														<img id="Codehighlighter1_58_145_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_58_145_Closed_Text.style.display='none'; Codehighlighter1_58_145_Open_Image.style.display='inline'; Codehighlighter1_58_145_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
												</a>
										</span>
										<span style="COLOR: rgb(0,0,255)">int</span>
										<span style="COLOR: rgb(0,0,0)"> sum(</span>
										<span style="COLOR: rgb(0,0,255)">int</span>
										<span style="COLOR: rgb(0,0,0)">[] a) </span>
										<span id="Codehighlighter1_58_145_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
												<a href="/Images/dot.gif" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
												</a>
										</span>
										<span id="Codehighlighter1_58_145_Open_Text">
												<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>    </span>
												<span style="COLOR: rgb(0,0,255)">int</span>
												<span style="COLOR: rgb(0,0,0)"> result </span>
												<span style="COLOR: rgb(0,0,0)">=</span>
												<span style="COLOR: rgb(0,0,0)"> </span>
												<span style="COLOR: rgb(0,0,0)">0</span>
												<span style="COLOR: rgb(0,0,0)">;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>    </span>
												<span style="COLOR: rgb(0,0,255)">for</span>
												<span style="COLOR: rgb(0,0,0)"> (</span>
												<span style="COLOR: rgb(0,0,255)">int</span>
												<span style="COLOR: rgb(0,0,0)"> i : a)<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>        result </span>
												<span style="COLOR: rgb(0,0,0)">+=</span>
												<span style="COLOR: rgb(0,0,0)"> i;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>    </span>
												<span style="COLOR: rgb(0,0,255)">return</span>
												<span style="COLOR: rgb(0,0,0)"> result;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>}</span>
										</span>
										<span style="COLOR: rgb(0,0,0)">
												<br />
												<a href="/Images/OutliningIndicators/None.gif" target="_blank">
														<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
												</a>
										</span>
								</div>
								<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left"> </p>
						</font>
				</o:p>
		</span>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">那么我们什么时候该用</span>
				<font face="宋体">
						<span lang="EN-US">for-each</span>
						<span style="FONT-FAMILY: 宋体">循环呢？只要情况运行就应该用，它真的让你的代码好看了很多。不幸的是，它有不能发挥作用的情形，就是需要用</span>
						<span lang="EN-US">iterator</span>
						<span style="FONT-FAMILY: 宋体">的</span>
						<span lang="EN-US">remove</span>
						<span style="FONT-FAMILY: 宋体">方法的时候，因为</span>
						<span lang="EN-US">iterator</span>
						<span style="FONT-FAMILY: 宋体">被隐藏了，你也无法调用它的方法了，新的循环不适用于过滤元素。同样的也不适用于需要把数组中的元素替换掉的情况。最后，它也不能在平行遍历多个容器的情况里使用，这些缺点，设计者是知道的，但是最后他们明智地选择这样一个简单的，能适用于多数情况的设计方案</span>
				</font>
		</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65021.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 11:20 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/65021.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>generic－泛型/类属（三）</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65018.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 03:17:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65018.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65018.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65018.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65018.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65018.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 管中窥虎																																																								在学习				java 1.5				的过程中，我使用了				sun				公布的				tutorial				，这份文档写的比较详尽易明，但是对于想快速了解				tiger				而且具有较好...&nbsp;&nbsp;<a href='http://www.blogjava.net/lijiajia418/archive/2006/08/22/65018.html'>阅读全文</a><img src ="http://www.blogjava.net/lijiajia418/aggbug/65018.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 11:17 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/65018.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自动包装机制</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65019.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 03:17:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65019.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65019.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65019.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65019.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65019.html</trackback:ping><description><![CDATA[
		<a>
				<span lang="EN-US"> 
<div class="postTitle"><b><span style="FONT-FAMILY: 宋体">管中窥虎</span></b><b><span lang="EN-US"><?XML:NAMESPACE PREFIX = O /?><o:p></o:p></span></b></div><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体">在学习</span><span lang="EN-US">java 1.5</span><span style="FONT-FAMILY: 宋体">的过程中，我使用了</span><span lang="EN-US">sun</span><span style="FONT-FAMILY: 宋体">公布的</span><span lang="EN-US">tutorial</span><span style="FONT-FAMILY: 宋体">，这份文档写的比较详尽易明，但是对于想快速了解</span><span lang="EN-US">tiger</span><span style="FONT-FAMILY: 宋体">而且具有较好</span><span lang="EN-US">java</span><span style="FONT-FAMILY: 宋体">基础的人来说，大篇幅的英文文档是比较耗时间和非必需的，所以我将会归纳这份文档的主要内容，在保证理解的底线上，尽力减少阅读者需要的时间。</span><span lang="EN-US"><o:p></o:p></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN-US"><o:p> </o:p></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体">在以下地址可以进入各新增语言特色介绍以及下载相关文档（若有）。</span><span lang="EN-US"><o:p></o:p></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN-US"><a href="http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html"><font color="#3468a4">http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html</font></a><o:p></o:p></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN-US"><o:p> </o:p></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"> <span lang="EN-US"><o:p></o:p></span></p><h1 style="MARGIN: 17pt 0cm 16.5pt"><font size="4"><h1 style="MARGIN: 17pt 0cm 16.5pt"><?XML:NAMESPACE PREFIX = ST1 /?><st1:chsdate w:st="on" year="2006" month="8" day="15" islunardate="False" isrocdate="False"><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">2006</span><span style="FONT-SIZE: 12pt">年</span><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">8</span><span style="FONT-SIZE: 12pt">月</span><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">15</span><span style="FONT-SIZE: 12pt">日</span></st1:chsdate><span style="FONT-SIZE: 12pt">星期二</span><span lang="EN-US" style="FONT-SIZE: 12pt"><o:p></o:p></span></h1><h1 style="MARGIN: 17pt 0cm 16.5pt"><span style="FONT-SIZE: 12pt">第三道虎纹：自动包装机制</span><span lang="EN-US" style="FONT-SIZE: 12pt"><o:p></o:p></span></h1></font></h1></span>
		</a>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">
				</span> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">我们知道容器类不能放基本类型的，放进放出都要先包装和解包，所有的这些工作都是繁琐而无聊的，它早就该有自动机制了，终于在</span>
				<font face="宋体">
						<span lang="EN-US">1.5</span>
						<span style="FONT-FAMILY: 宋体">里得到了实现。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体"> </font>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">
				</span>
		</p>
		<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
				<a href="/Images/OutliningIndicators/None.gif" target="_blank">
						<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
				</a>
				<span style="COLOR: rgb(0,0,255)">import</span>
				<span style="COLOR: rgb(0,0,0)"> java.util.</span>
				<span style="COLOR: rgb(0,0,0)">*</span>
				<span style="COLOR: rgb(0,0,0)">;<br /><a href="/Images/OutliningIndicators/None.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/None.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a> <br /><a href="/Images/OutliningIndicators/None.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/None.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a></span>
				<span style="COLOR: rgb(0,128,0)">//</span>
				<span style="COLOR: rgb(0,128,0)"> Prints a frequency table of the words on the command line</span>
				<span style="COLOR: rgb(0,128,0)">
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
				<span style="COLOR: rgb(0,0,0)">
						<br />
						<a href="/Images/OutliningIndicators/ExpandedBlockStart.gif" target="_blank">
								<img id="Codehighlighter1_109_394_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_109_394_Open_Text.style.display='none'; Codehighlighter1_109_394_Closed_Image.style.display='inline'; Codehighlighter1_109_394_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<a href="/Images/OutliningIndicators/ContractedBlock.gif" target="_blank">
								<img id="Codehighlighter1_109_394_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_109_394_Closed_Text.style.display='none'; Codehighlighter1_109_394_Open_Image.style.display='inline'; Codehighlighter1_109_394_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
				<span style="COLOR: rgb(0,0,255)">public</span>
				<span style="COLOR: rgb(0,0,0)"> </span>
				<span style="COLOR: rgb(0,0,255)">class</span>
				<span style="COLOR: rgb(0,0,0)"> Frequency </span>
				<span id="Codehighlighter1_109_394_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
						<a href="/Images/dot.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
						</a>
				</span>
				<span id="Codehighlighter1_109_394_Open_Text">
						<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank"><img id="Codehighlighter1_154_391_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_154_391_Open_Text.style.display='none'; Codehighlighter1_154_391_Closed_Image.style.display='inline'; Codehighlighter1_154_391_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank"><img id="Codehighlighter1_154_391_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_154_391_Closed_Text.style.display='none'; Codehighlighter1_154_391_Open_Image.style.display='inline'; Codehighlighter1_154_391_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>   </span>
						<span style="COLOR: rgb(0,0,255)">public</span>
						<span style="COLOR: rgb(0,0,0)"> </span>
						<span style="COLOR: rgb(0,0,255)">static</span>
						<span style="COLOR: rgb(0,0,0)"> </span>
						<span style="COLOR: rgb(0,0,255)">void</span>
						<span style="COLOR: rgb(0,0,0)"> main(String[] args) </span>
						<span id="Codehighlighter1_154_391_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
								<a href="/Images/dot.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
								</a>
						</span>
						<span id="Codehighlighter1_154_391_Open_Text">
								<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>      Map</span>
								<span style="COLOR: rgb(0,0,0)">&lt;</span>
								<span style="COLOR: rgb(0,0,0)">String, Integer</span>
								<span style="COLOR: rgb(0,0,0)">&gt;</span>
								<span style="COLOR: rgb(0,0,0)"> m </span>
								<span style="COLOR: rgb(0,0,0)">=</span>
								<span style="COLOR: rgb(0,0,0)"> </span>
								<span style="COLOR: rgb(0,0,255)">new</span>
								<span style="COLOR: rgb(0,0,0)"> TreeMap</span>
								<span style="COLOR: rgb(0,0,0)">&lt;</span>
								<span style="COLOR: rgb(0,0,0)">String, Integer</span>
								<span style="COLOR: rgb(0,0,0)">&gt;</span>
								<span style="COLOR: rgb(0,0,0)">();<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank"><img id="Codehighlighter1_252_355_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_252_355_Open_Text.style.display='none'; Codehighlighter1_252_355_Closed_Image.style.display='inline'; Codehighlighter1_252_355_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank"><img id="Codehighlighter1_252_355_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_252_355_Closed_Text.style.display='none'; Codehighlighter1_252_355_Open_Image.style.display='inline'; Codehighlighter1_252_355_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>      </span>
								<span style="COLOR: rgb(0,0,255)">for</span>
								<span style="COLOR: rgb(0,0,0)"> (String word : args) </span>
								<span id="Codehighlighter1_252_355_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
										<a href="/Images/dot.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
										</a>
								</span>
								<span id="Codehighlighter1_252_355_Open_Text">
										<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>          Integer freq </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> m.get(word);<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>          m.put(word, (freq </span>
										<span style="COLOR: rgb(0,0,0)">==</span>
										<span style="COLOR: rgb(0,0,0)"> </span>
										<span style="COLOR: rgb(0,0,255)">null</span>
										<span style="COLOR: rgb(0,0,0)"> </span>
										<span style="COLOR: rgb(0,0,0)">?</span>
										<span style="COLOR: rgb(0,0,0)"> </span>
										<span style="COLOR: rgb(0,0,0)">1</span>
										<span style="COLOR: rgb(0,0,0)"> : freq </span>
										<span style="COLOR: rgb(0,0,0)">+</span>
										<span style="COLOR: rgb(0,0,0)"> </span>
										<span style="COLOR: rgb(0,0,0)">1</span>
										<span style="COLOR: rgb(0,0,0)">));<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>      }</span>
								</span>
								<span style="COLOR: rgb(0,0,0)">
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>      System.out.println(m);<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>   }</span>
						</span>
						<span style="COLOR: rgb(0,0,0)">
								<br />
								<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
								</a>
								<br />
								<a href="/Images/OutliningIndicators/ExpandedBlockEnd.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
								</a>}</span>
				</span>
				<span style="COLOR: rgb(0,0,0)">
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
		</div>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">java Frequency if it is to be it is up to me to do the watusi<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">{be=1, do=1, if=1, is=2, it=2, me=1, the=1, to=3, up=1, watusi=1}<o:p></o:p></span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体"> </font>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">注意到</span>
				<font face="宋体">
						<span lang="EN-US">freq</span>
						<span style="FONT-FAMILY: 宋体">如果为空，那么</span>
						<span lang="EN-US">put</span>
						<span style="FONT-FAMILY: 宋体">的第二个参数就是</span>
						<span lang="EN-US">int</span>
						<span style="FONT-FAMILY: 宋体">类型的</span>
						<span lang="EN-US">1</span>
						<span style="FONT-FAMILY: 宋体">，这个时候就出现了自动的包装，而如果</span>
						<span lang="EN-US">freq</span>
						<span style="FONT-FAMILY: 宋体">不为空，那么</span>
						<span lang="EN-US">freq</span>
						<span style="FONT-FAMILY: 宋体">＋</span>
						<span lang="EN-US">1</span>
						<span style="FONT-FAMILY: 宋体">就是自动解包后运算，再自动包装，放入</span>
						<span lang="EN-US">Map</span>
						<span style="FONT-FAMILY: 宋体">中。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">现在你基本上可以忽略</span>
				<font face="宋体">
						<span lang="EN-US">Integer</span>
						<span style="FONT-FAMILY: 宋体">和</span>
						<span lang="EN-US">int</span>
						<span style="FONT-FAMILY: 宋体">（或者这一类的对应）之间的区别了，除了要注意几点警告：</span>
						<span lang="EN-US">Integer</span>
						<span style="FONT-FAMILY: 宋体">是可以为</span>
						<span lang="EN-US">null</span>
						<span style="FONT-FAMILY: 宋体">的，如果程序试图自动解包一个</span>
						<span lang="EN-US">null</span>
						<span style="FONT-FAMILY: 宋体">，会抛出</span>
						<span lang="EN-US">NullPointerException</span>
						<span style="FONT-FAMILY: 宋体">。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">＝＝用于</span>
				<font face="宋体">
						<span lang="EN-US">Integer</span>
						<span style="FONT-FAMILY: 宋体">时比的是引用，用于</span>
						<span lang="EN-US">int</span>
						<span style="FONT-FAMILY: 宋体">的时候比的是值。最后，还有一点就是即使现在是自动解包打包，它们的运行损耗并没消失，你依然为这些动作付出了</span>
						<span lang="EN-US">cpu</span>
						<span style="FONT-FAMILY: 宋体">的计算时间。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体"> </font>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">这里还有一个相关的例子：</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体">
								</font>
						</o:p>
				</span>
		</p>
		<div style="BORDER-RIGHT: rgb(204,204,204) 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: rgb(204,204,204) 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: rgb(204,204,204) 1px solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: rgb(204,204,204) 1px solid; BACKGROUND-COLOR: rgb(238,238,238)">
				<a href="/Images/OutliningIndicators/None.gif" target="_blank">
						<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
				</a>
				<span style="COLOR: rgb(0,128,0)">//</span>
				<span style="COLOR: rgb(0,128,0)"> List adapter for primitive int array</span>
				<span style="COLOR: rgb(0,128,0)">
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
				<span style="COLOR: rgb(0,0,0)">
						<br />
						<a href="/Images/OutliningIndicators/ExpandedBlockStart.gif" target="_blank">
								<img id="Codehighlighter1_91_449_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_91_449_Open_Text.style.display='none'; Codehighlighter1_91_449_Closed_Image.style.display='inline'; Codehighlighter1_91_449_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<a href="/Images/OutliningIndicators/ContractedBlock.gif" target="_blank">
								<img id="Codehighlighter1_91_449_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_91_449_Closed_Text.style.display='none'; Codehighlighter1_91_449_Open_Image.style.display='inline'; Codehighlighter1_91_449_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
				<span style="COLOR: rgb(0,0,255)">public</span>
				<span style="COLOR: rgb(0,0,0)"> </span>
				<span style="COLOR: rgb(0,0,255)">static</span>
				<span style="COLOR: rgb(0,0,0)"> List</span>
				<span style="COLOR: rgb(0,0,0)">&lt;</span>
				<span style="COLOR: rgb(0,0,0)">Integer</span>
				<span style="COLOR: rgb(0,0,0)">&gt;</span>
				<span style="COLOR: rgb(0,0,0)"> asList(</span>
				<span style="COLOR: rgb(0,0,255)">final</span>
				<span style="COLOR: rgb(0,0,0)"> </span>
				<span style="COLOR: rgb(0,0,255)">int</span>
				<span style="COLOR: rgb(0,0,0)">[] a) </span>
				<span id="Codehighlighter1_91_449_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
						<a href="/Images/dot.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
						</a>
				</span>
				<span id="Codehighlighter1_91_449_Open_Text">
						<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank"><img id="Codehighlighter1_133_445_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_133_445_Open_Text.style.display='none'; Codehighlighter1_133_445_Closed_Image.style.display='inline'; Codehighlighter1_133_445_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank"><img id="Codehighlighter1_133_445_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_133_445_Closed_Text.style.display='none'; Codehighlighter1_133_445_Open_Image.style.display='inline'; Codehighlighter1_133_445_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>    </span>
						<span style="COLOR: rgb(0,0,255)">return</span>
						<span style="COLOR: rgb(0,0,0)"> </span>
						<span style="COLOR: rgb(0,0,255)">new</span>
						<span style="COLOR: rgb(0,0,0)"> AbstractList</span>
						<span style="COLOR: rgb(0,0,0)">&lt;</span>
						<span style="COLOR: rgb(0,0,0)">Integer</span>
						<span style="COLOR: rgb(0,0,0)">&gt;</span>
						<span style="COLOR: rgb(0,0,0)">() </span>
						<span id="Codehighlighter1_133_445_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
								<a href="/Images/dot.gif" target="_blank">
										<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
								</a>
						</span>
						<span id="Codehighlighter1_133_445_Open_Text">
								<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank"><img id="Codehighlighter1_170_185_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_170_185_Open_Text.style.display='none'; Codehighlighter1_170_185_Closed_Image.style.display='inline'; Codehighlighter1_170_185_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank"><img id="Codehighlighter1_170_185_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_170_185_Closed_Text.style.display='none'; Codehighlighter1_170_185_Open_Image.style.display='inline'; Codehighlighter1_170_185_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>        </span>
								<span style="COLOR: rgb(0,0,255)">public</span>
								<span style="COLOR: rgb(0,0,0)"> Integer get(</span>
								<span style="COLOR: rgb(0,0,255)">int</span>
								<span style="COLOR: rgb(0,0,0)"> i) </span>
								<span id="Codehighlighter1_170_185_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
										<a href="/Images/dot.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
										</a>
								</span>
								<span id="Codehighlighter1_170_185_Open_Text">
										<span style="COLOR: rgb(0,0,0)">{ </span>
										<span style="COLOR: rgb(0,0,255)">return</span>
										<span style="COLOR: rgb(0,0,0)"> a[i]; }</span>
								</span>
								<span style="COLOR: rgb(0,0,0)">
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>        </span>
								<span style="COLOR: rgb(0,128,0)">//</span>
								<span style="COLOR: rgb(0,128,0)"> Throws NullPointerException if val == null</span>
								<span style="COLOR: rgb(0,128,0)">
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
								</span>
								<span style="COLOR: rgb(0,0,0)">
										<br />
										<a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank">
												<img id="Codehighlighter1_290_390_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_290_390_Open_Text.style.display='none'; Codehighlighter1_290_390_Closed_Image.style.display='inline'; Codehighlighter1_290_390_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank">
												<img id="Codehighlighter1_290_390_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_290_390_Closed_Text.style.display='none'; Codehighlighter1_290_390_Open_Image.style.display='inline'; Codehighlighter1_290_390_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>        </span>
								<span style="COLOR: rgb(0,0,255)">public</span>
								<span style="COLOR: rgb(0,0,0)"> Integer set(</span>
								<span style="COLOR: rgb(0,0,255)">int</span>
								<span style="COLOR: rgb(0,0,0)"> i, Integer val) </span>
								<span id="Codehighlighter1_290_390_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
										<a href="/Images/dot.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
										</a>
								</span>
								<span id="Codehighlighter1_290_390_Open_Text">
										<span style="COLOR: rgb(0,0,0)">{<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>            Integer oldVal </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> a[i];<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>            a[i] </span>
										<span style="COLOR: rgb(0,0,0)">=</span>
										<span style="COLOR: rgb(0,0,0)"> val;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>            </span>
										<span style="COLOR: rgb(0,0,255)">return</span>
										<span style="COLOR: rgb(0,0,0)"> oldVal;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>        }</span>
								</span>
								<span style="COLOR: rgb(0,0,0)">
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<br />
										<a href="/Images/OutliningIndicators/ExpandedSubBlockStart.gif" target="_blank">
												<img id="Codehighlighter1_419_438_Open_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" onclick="this.style.display='none'; Codehighlighter1_419_438_Open_Text.style.display='none'; Codehighlighter1_419_438_Closed_Image.style.display='inline'; Codehighlighter1_419_438_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<a href="/Images/OutliningIndicators/ContractedSubBlock.gif" target="_blank">
												<img id="Codehighlighter1_419_438_Closed_Image" onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_419_438_Closed_Text.style.display='none'; Codehighlighter1_419_438_Open_Image.style.display='inline'; Codehighlighter1_419_438_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>        </span>
								<span style="COLOR: rgb(0,0,255)">public</span>
								<span style="COLOR: rgb(0,0,0)"> </span>
								<span style="COLOR: rgb(0,0,255)">int</span>
								<span style="COLOR: rgb(0,0,0)"> size() </span>
								<span id="Codehighlighter1_419_438_Closed_Text" style="BORDER-RIGHT: rgb(128,128,128) 1px solid; BORDER-TOP: rgb(128,128,128) 1px solid; DISPLAY: none; BORDER-LEFT: rgb(128,128,128) 1px solid; BORDER-BOTTOM: rgb(128,128,128) 1px solid; BACKGROUND-COLOR: rgb(255,255,255)">
										<a href="/Images/dot.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/dot.gif" onload="java_script_:if(this.width&gt;500)this.width=500" border="0" />
										</a>
								</span>
								<span id="Codehighlighter1_419_438_Open_Text">
										<span style="COLOR: rgb(0,0,0)">{ </span>
										<span style="COLOR: rgb(0,0,255)">return</span>
										<span style="COLOR: rgb(0,0,0)"> a.length; }</span>
								</span>
								<span style="COLOR: rgb(0,0,0)">
										<br />
										<a href="/Images/OutliningIndicators/InBlock.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>
										<br />
										<a href="/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" target="_blank">
												<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
										</a>    }</span>
						</span>
						<span style="COLOR: rgb(0,0,0)">;<br /><a href="/Images/OutliningIndicators/InBlock.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a><br /><a href="/Images/OutliningIndicators/ExpandedBlockEnd.gif" target="_blank"><img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" /></a>}</span>
				</span>
				<span style="COLOR: rgb(0,0,0)">
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
						<br />
						<a href="/Images/OutliningIndicators/None.gif" target="_blank">
								<img onmousewheel="return bbimg(this)" title="点击在新窗口查看原始图片" src="http://www.blogjava.net/images/OutliningIndicators/None.gif" onload="java_script_:if(this.width&gt;500)this.width=500" align="top" border="0" />
						</a>
				</span>
		</div>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">通过自动的包装机制，提供了数组与</span>
				<font face="宋体">
						<span lang="EN-US">List</span>
						<span style="FONT-FAMILY: 宋体">的灵活转换，但是它的运行效率是比较低的，每个</span>
						<span lang="EN-US">get</span>
						<span style="FONT-FAMILY: 宋体">，</span>
						<span lang="EN-US">set</span>
						<span style="FONT-FAMILY: 宋体">操作，都进行了解包或者打包，偶尔使用这个方法还凑合，如果是用于核心代码的循环里，那就是够傻的了。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体"> </font>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体">那我们什么时候该用自动的包装机制呢？仅仅是用于消除这类所谓的“阻抗不匹配”，就是基本类型与包装类的差异，例如要把数值放入容器类的时候。如果在进行科学计算的代码或者其他讲究效率的代码中使用，则是不恰当的。一个</span>
				<font face="宋体">
						<span lang="EN-US">Integer</span>
						<span style="FONT-FAMILY: 宋体">不是一个</span>
						<span lang="EN-US">int</span>
						<span style="FONT-FAMILY: 宋体">，自动包装机制仅仅模糊了它们的区别，而没有消除之。</span>
				</font>
		</p>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65019.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 11:17 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/65019.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>generic－泛型/类属（二）</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65017.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Tue, 22 Aug 2006 03:14:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65017.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65017.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/22/65017.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65017.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65017.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 管中窥虎																																																								在学习										java 1.5						的过程中，我使用了						sun						公布的						tutorial						，这份文档写的比较详尽易明，但是对于想快速了解					...&nbsp;&nbsp;<a href='http://www.blogjava.net/lijiajia418/archive/2006/08/22/65017.html'>阅读全文</a><img src ="http://www.blogjava.net/lijiajia418/aggbug/65017.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-22 11:14 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/22/65017.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>