﻿<?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-study-随笔分类-线程</title><link>http://www.blogjava.net/xixidabao/category/15377.html</link><description>GROW WITH JAVA</description><language>zh-cn</language><lastBuildDate>Thu, 23 Aug 2007 12:19:30 GMT</lastBuildDate><pubDate>Thu, 23 Aug 2007 12:19:30 GMT</pubDate><ttl>60</ttl><item><title>JAVA教程：解析Java的多线程机制</title><link>http://www.blogjava.net/xixidabao/archive/2006/09/12/69221.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Tue, 12 Sep 2006 11:50:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/09/12/69221.html</guid><description><![CDATA[一、进程与应用程序的区别 <br />　　 <br />　　进程（Process）是最初定义在Unix等多用户、多任务<a href="http://dev.21tx.com/os/" target="_blank"><font color="#3366cc">操作系统</font></a>环境下用于表示应用程序在内存环境中基本执行单元的概念。以Unix操作系统为例，进程是Unix操作系统环境中的基本成分、是系统资源分配的基本单位。Unix操作系统中完成的几乎所有用户管理和资源分配等工作都是通过操作系统对应用程序进程的控制来实现的。 <br />　　 <br />　　C、<a href="http://dev.21tx.com/language/c/" target="_blank"><font color="#3366cc">C++</font></a>、<a href="http://dev.21tx.com/java/" target="_blank"><font color="#3366cc">Java</font></a>等语言编写的源程序经相应的编译器编译成可执行文件后，提交给计算机处理器运行。这时，处在可执行状态中的应用程序称为进程。从用户角度来看，进程是应用程序的一个执行过程。从操作系统核心角度来看，进程代表的是操作系统分配的内存、CPU时间片等资源的基本单位，是为正在运行的程序提供的运行环境。进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中，而进程则是处于动态条件下由操作系统维护的系统资源管理实体。多任务环境下应用程序进程的主要特点包括： <br />　　 <br />　　●进程在执行过程中有内存单元的初始入口点，并且进程存活过程中始终拥有独立的内存地址空间； <br />　　 <br />　　●进程的生存期状态包括创建、就绪、运行、阻塞和死亡等类型； <br />　　 <br />　　●从应用程序进程在执行过程中向CPU发出的运行指令形式不同，可以将进程的状态分为用户态和核心态。处于用户态下的进程执行的是应用程序指令、处于核心态下的应用程序进程执行的是操作系统指令。 <br />　　 <br />　　在Unix操作系统启动过程中，系统自动创建swapper、init等系统进程，用于管理内存资源以及对用户进程进行调度等。在Unix环境下无论是由操作系统创建的进程还要由应用程序执行创建的进程，均拥有唯一的进程标识（PID）。 <br /><br />二、进程与Java<a href="http://dev.21tx.com/java/adv/thread/" target="_blank"><font color="#3366cc">线程</font></a>的区别 <br />　　 <br />　　 <br />　　应用程序在执行过程中存在一个内存空间的初始入口点地址、一个程序执行过程中的代码执行序列以及用于标识进程结束的内存出口点地址，在进程执行过程中的每一时间点均有唯一的处理器指令与内存单元地址相对应。 <br />　　 <br />　　Java语言中定义的线程（Thread）同样包括一个内存入口点地址、一个出口点地址以及能够顺序执行的代码序列。但是进程与线程的重要区别在于线程不能够单独执行，它必须运行在处于活动状态的应用程序进程中，因此可以定义线程是程序内部的具有并发性的顺序代码流。 <br />　　 <br />　　Unix操作系统和Microsoft <a href="http://dev.21tx.com/os/windows/" target="_blank"><font color="#3366cc">Windows</font></a>操作系统支持多用户、多进程的并发执行，而Java语言支持应用程序进程内部的多个执行线程的并发执行。多线程的意义在于一个应用程序的多个逻辑单元可以并发地执行。但是多线程并不意味着多个用户进程在执行，操作系统也不把每个线程作为独立的进程来分配独立的系统资源。进程可以创建其子进程，子进程与父进程拥有不同的可执行代码和数据内存空间。而在用于代表应用程序的进程中多个线程共享数据内存空间，但保持每个线程拥有独立的执行堆栈和程序执行上下文（Context）。 <br />　　 <br />　　基于上述区别，线程也可以称为轻型进程 (Light Weight Process，LWP)。不同线程间允许任务协作和数据交换，使得在计算机系统资源消耗等方面非常廉价。 <br />　　 <br />　　线程需要操作系统的支持，不是所有类型的计算机都支持多线程应用程序。Java程序设计语言将线程支持与语言运行环境结合在一起，提供了多任务并发执行的能力。这就好比一个人在处理家务的过程中，将衣服放到洗衣机中自动洗涤后将大米放在电饭锅里，然后开始做菜。等菜做好了，饭熟了同时衣服也洗好了。 <br />　　 <br />　　需要注意的是：在应用程序中使用多线程不会增加 CPU 的数据处理能力。只有在多CPU 的计算机或者在网络计算体系结构下，将Java程序划分为多个并发执行线程后，同时启动多个线程运行，使不同的线程运行在基于不同处理器的Java虚拟机中，才能提高应用程序的执行效率。<br /><br />另外，如果应用程序必须等待网络连接或<a href="http://dev.21tx.com/database/" target="_blank"><font color="#3366cc">数据库</font></a>连接等数据吞吐速度相对较慢的资源时，多线程应用程序是非常有利的。基于Internet的应用程序有必要是多线程类型的，例如，当开发要支持大量客户机的<a href="http://www.21tx.com/server/" target="_blank"><font color="#3366cc">服务器</font></a>端应用程序时，可以将应用程序创建成多线程形式来响应客户端的连接请求，使每个连接用户独占一个客户端连接线程。这样，用户感觉服务器只为连接用户自己服务，从而缩短了服务器的客户端响应时间。 <br />　　 <br />　　 <br />三、Java语言的多线程程序设计方法 <br />　　 <br />　　 <br />　　利用Java语言实现多线程应用程序的方法很简单。根据多线程应用程序继承或实现对象的不同可以采用两种方式：一种是应用程序的并发运行对象直接继承Java的线程类Thread；另外一种方式是定义并发执行对象实现Runnable接口。 <br />　　 <br />　　继承Thread类的多线程程序设计方法 <br />　　 <br />　　Thread 类是<a href="http://dev.21tx.com/java/base/jdk/" target="_blank"><font color="#3366cc">JDK</font></a>中定义的用于控制线程对象的类，在该类中封装了用于进行线程控制的方法。见下面的示例代码： <br />　　 <br />　　[code]//Consumer.java <br />　　import java.util.*; <br />　　class Consumer extends Thread <br />　　{ <br />　　 int nTime; <br />　　 String strConsumer; <br />　　 public Consumer(int nTime, String strConsumer) <br />　　 { <br />　　 this.nTime = nTime; <br />　　 this.strConsumer = strConsumer; <br />　　 } <br />　　 public void run() <br />　　 { <br />　　while(true) <br />　　{ <br />　　 try <br />　　{ <br />　　 System.out.println("Consumer name:"+strConsumer+"\n"); <br />　　 Thread.sleep(nTime); <br />　　 } <br />　　catch(Exception e) <br />　　{ <br />　　 e.printStackTrace(); <br />　　 } <br />　　} <br />　　 } <br />　　static public void main(String args[]) <br />　　{ <br />　　 Consumer aConsumer = new Consumer (1000, "aConsumer"); <br />　　 aConsumer.start(); <br />　　 Consumer bConsumer = new Consumer (2000, "bConsumer"); <br />　　 bConsumer.start(); <br />　　 Consumer cConsumer = new Consumer (3000, "cConsumer "); <br />　　 cConsumer.start(); <br />　　} <br />　　} [/code]<br />　　 <br />　　 <br />　　 <br />　　 <br />　　从上面的程序代码可以看出：多线程执行地下Consumer继承Java语言中的线程类Thread并且在main方法中创建了三个Consumer对象的实例。当调用对象实例的start方法时，自动调用Consumer类中定义的run方法启动对象线程运行。线程运行的结果是每间隔nTime时间打印出对象实例中的字符串成员变量strConsumer的内容。 <br />　　 <br />　　可以总结出继承Thread类的多线程程序设计方法是使应用程序类继承Thread类并且在该类的run方法中实现并发性处理过程。 <br />　　 <br />　　实现Runnable接口的多线程程序设计方法 <br />　　 <br />　　Java语言中提供的另外一种实现多线程应用程序的方法是多线程对象实现Runnable接口并且在该类中定义用于启动线程的run方法。这种定义方式的好处在于多线程应用对象可以继承其它对象而不是必须继承Thread类，从而能够增加类定义的逻辑性。 <br />　　 <br />　　实现Runnable接口的多线程应用程序框架代码如下所示： <br />　　 <br />　　//Consumer.java <br />　　import java.util.*; <br />　　class Consumer implements Runnable <br />　　{ <br />　　 … … <br />　　public Consumer(int nTime, String strConsumer){… …} <br />　　public void run(){… …} <br />　　static public void main(String args[]) <br />　　{ <br />　　Thread aConsumer = new Thread(new Consumer(1000, "aConsumer")); <br />　　aConsumer.start(); <br />　　//其它对象实例的运行线程 <br />　　 //… … <br />　　 } <br />　　} <br />　　 <br />　　从上述代码可以看出：该类实现了Runnable接口并且在该类中定义了run方法。这种多线程应用程序的实现方式与继承Thread类的多线程应用程序的重要区别在于启动多线程对象的方法设计方法不同。在上述代码中，通过创建Thread对象实例并且将应用对象作为创建Thread类实例的参数。 <br /><br />四、线程间的同步 <br />　　 <br />　　Java应用程序的多个线程共享同一进程的数据资源，多个用户线程在并发运行过程中可能同时访问具有敏感性的内容。在Java中定义了线程同步的概念，实现对共享资源的一致性维护。下面以笔者最近开发的移动<a href="http://news.21tx.com/telcom/" target="_blank"><font color="#3366cc">通信</font></a>计费系统中线程间同步控制方法，说明Java语言中多线程同步方式的实现过程。 <br />　　 <br />　　在没有多线程同步控制策略条件下的客户账户类定义框架代码如下所示： <br />　　 <br />　　public class Re<a href="http://dev.21tx.com/corp/gis/" target="_blank"><font color="#3366cc">GIS</font></a>terAccount <br />　　{ <br />　　float fBalance; <br />　　//客户缴费方法 <br />　　public void deposit(float fFees){ fBalance += fFees; } <br />　　//通话计费方法 <br />　　public void withdraw(float fFees){ fBalance -= fFees; } <br />　　… … <br />　　} <br /><br />　　 <br />　　 <br />　　 <br />　　 <br />　　读者也许会认为：上述程序代码完全能够满足计费系统实际的需要。确实，在单线程环境下该程序确实是可靠的。但是，多进程并发运行的情况是怎样的呢？假设发生这种情况：客户在客户服务中心进行缴费的同时正在利用移动通信设备仅此通话，客户通话结束时计费系统启动计费进程，而同时服务中心的工作人员也提交缴费进程运行。读者可以看到如果发生这种情况，对客户账户的处理是不严肃的。 <br />　　 <br />　　如何解决这种问题呢？很简单，在RegisterAccount类方法定义中加上用于标识同步方法的关键字synchronized。这样，在同步方法执行过程中该方法涉及的共享资源（在上述代码中为fBalance成员变量）将被加上共享锁，以确保在方法运行期间只有该方法能够对共享资源进行访问，直到该方法的线程运行结束打开共享锁，其它线程才能够访问这些共享资源。在共享锁没有打开的时候其它访问共享资源的线程处于阻塞状态。 <br />　　 <br />　　进行线程同步策略控制后的RegisterAccount类定义如下面代码所示： <br />　　 <br />　　public class RegisterAccount <br />　　{ <br />　　float fBalance; <br />　　public synchronized void deposit(float fFees){ fBalance += fFees; } <br />　　public synchronized void withdraw(float fFees){ fBalance -= fFees; } <br />　　… … <br />　　} <br /><br />　　 <br />　　从经过线程同步机制定义后的代码形式可以看出：在对共享资源进行访问的方法访问属性关键字（public）后附加同步定义关键字synchronized，使得同步方法在对共享资源访问的时候，为这些敏感资源附加共享锁来控制方法执行期间的资源独占性，实现了应用系统数据资源的一致性管理和维护。 <br /><br /><br />五、 Java线程的管理 <br />　　 <br />　　 <br />　　线程的状态控制 <br />　　 <br />　　在这里需要明确的是：无论采用继承Thread类还是实现Runnable接口来实现应用程序的多线程能力，都需要在该类中定义用于完成实际功能的run方法，这个run方法称为线程体（Thread Body）。按照线程体在计算机系统内存中的状态不同，可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为： <br />　　 <br />　　创建状态：当利用new关键字创建线程对象实例后，它仅仅作为一个对象实例存在，JVM没有为其分配CPU时间片等线程运行资源； <br />　　 <br />　　就绪状态：在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时，线程已经得到除CPU时间之外的其它系统资源，只等JVM的线程调度器按照线程的优先级对该线程进行调度，从而使该线程拥有能够获得CPU时间片的机会。 <br />　　 <br />　　睡眠状态：在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时，该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后，线程重新由JVM线程调度器进行调度和管理。 <br />　　 <br />　　挂起状态：可以通过调用suspend方法将线程的状态转换为挂起状态。这时，线程将释放占用的所有资源，由JVM调度转入临时存储空间，直至应用程序调用resume方法恢复线程运行。 <br />　　 <br />　　死亡状态：当线程体运行结束或者调用线程对象的stop方法后线程将终止运行，由JVM收回线程占用的资源。 <br />　　 <br />　　在Java线程类中分别定义了相应的方法，用于在应用程序中对线程状态进行控制和管理。 <br />　　 <br />　　线程的调度 <br />　　 <br />　　线程调用的意义在于JVM应对运行的多个线程进行系统级的协调，以避免多个线程争用有限资源而导致应用系统死机或者崩溃。 <br />　　 <br />　　为了线程对于操作系统和用户的重要性区分开，Java定义了线程的优先级策略。Java将线程的优先级分为10个等级，分别用1-10之间的数字表示。数字越大表明线程的级别越高。相应地，在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY，代表的优先级等级分别为1、10和5。当一个线程对象被创建时，其默认的线程优先级是5。 <br />　　 <br />　　为了控制线程的运行策略，Java定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器按照线程的优先级决定那个线程投入处理器运行。在多个线程处于就绪状态的条件下，具有高优先级的线程会在低优先级线程之前得到执行。线程调度器同样采用"抢占式"策略来调度线程执行，即当前线程执行过程中有较高优先级的线程进入就绪状态，则高优先级的线程立即被调度执行。具有相同优先级的所有线程采用轮转的方式来共同分配CPU时间片。 <br />　　 <br />　　在应用程序中设置线程优先级的方法很简单，在创建线程对象之后可以调用线程对象的setPriority方法改变该线程的运行优先级，同样可以调用getPriority方法获取当前线程的优先级。 <br />　　 <br />　　在Java中比较特殊的线程是被称为守护（Daemon）线程的低级别线程。这个线程具有最低的优先级，用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。典型的守护线程例子是JVM中的系统资源自动回收线程，它始终在低级别的状态中运行，用于实时监控和管理系统中的可回收资源。 <br />　　 <br />　　线程分组管理 <br />　　 <br />　　Java定义了在多线程运行系统中的线程组（ThreadGroup）对象，用于实现按照特定功能对线程进行集中式分组管理。用户创建的每个线程均属于某线程组，这个线程组可以在线程创建时指定，也可以不指定线程组以使该线程处于默认的线程组之中。但是，一旦线程加入某线程组，该线程就一直存在于该线程组中直至线程死亡，不能在中途改变线程所属的线程组。 <br />　　 <br />　　当Java的Application应用程序运行时，JVM创建名称为main的线程组。除非单独指定，在该应用程序中创建的线程均属于main线程组。在main线程组中可以创建其它名称的线程组并将其它线程加入到该线程组中，依此类推，构成线程和线程组之间的树型管理和继承关系。 <br />　　 <br />　　与线程类似，可以针对线程组对象进行线程组的调度、状态管理以及优先级设置等。在对线程组进行管理过程中，加入到某线程组中的所有线程均被看作统一的对象。 <br /><br />六、小结：<br />本文针对Java平台中线程的性质和应用程序的多线程策略进行了分析和讲解。 <br />　　 <br />　　与其它操作系统环境不同，Java运行环境中的线程类似于多用户、多任务操作系统环境下的进程，但在进程和线程的运行及创建方式等方面，进程与Java线程具有明显区别。 <br />　　 <br />　　Unix操作系统环境下，应用程序可以利用fork函数创建子进程，但子进程与该应用程序进程拥有独立的地址空间、系统资源和代码执行单元，并且进程的调度是由操作系统来完成的，使得在应用进程之间进行通信和线程协调相对复杂。而Java应用程序中的多线程则是共享同一应用系统资源的多个并行代码执行体，线程之间的通信和协调方法相对简单。 <br />　　 <br />　　可以说：Java语言对应用程序多线程能力的支持增强了Java作为网络程序设计语言的优势，为实现分布式应用系统中多客户端的并发访问以及提高服务器的响应效率奠定坚实<a href="http://dev.21tx.com/java/base/" target="_blank"><font color="#3366cc">基础</font></a>。<br /><img src ="http://www.blogjava.net/xixidabao/aggbug/69221.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-09-12 19:50 <a href="http://www.blogjava.net/xixidabao/archive/2006/09/12/69221.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java多线程设计模式</title><link>http://www.blogjava.net/xixidabao/archive/2006/06/18/53641.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Sun, 18 Jun 2006 13:38:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/06/18/53641.html</guid><description><![CDATA[<table cellSpacing=0 cellPadding=0 width=760 align=center border=0>
    <tbody>
        <tr>
            <td class=title vAlign=center align=middle height=56><strong><font color=#ff0000 size=3>java多线程设计模式<br><!-- #EndEditable --></font></strong></td>
        </tr>
        <tr>
            <td class=formtitle align=middle height=40><!-- #BeginEditable "2" -->zdmilan 的 Blog <br><!-- #EndEditable --></td>
        </tr>
    </tbody>
</table>
<table height=65 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
    <tbody>
        <tr>
            <td class=content height=65><!-- #BeginEditable "3" -->
            <table width="100%" align=center>
                <tbody>
                    <tr>
                        <td class=content>
                        <p>java语言已经内置了多线程支持，所有实现Runnable接口的类都可被启动一个新线程，新线程会执行该实例的run()方法，当run()方法执行完毕后，线程就结束了。一旦一个线程执行完毕，这个实例就不能再重新启动，只能重新生成一个新实例，再启动一个新线程。 </p>
                        <p>Thread类是实现了Runnable接口的一个实例，它代表一个线程的实例，并且，启动线程的唯一方法就是通过Thread类的start()实例方法：</p>
                        <p>Thread t = new Thread();<br>t.start();</p>
                        <p>start()方法是一个native方法，它将启动一个新线程，并执行run()方法。Thread类默认的run()方法什么也不做就退出了。注意：直接调用run()方法并不会启动一个新线程，它和调用一个普通的java方法没有什么区别。</p>
                        <p>因此，有两个方法可以实现自己的线程：</p>
                        <p>方法1：自己的类extend Thread，并复写run()方法，就可以启动新线程并执行自己定义的run()方法。例如：</p>
                        <p>public class MyThread extends Thread {<br>public run() {<br>System.out.println("MyThread.run()");<br>}<br>}</p>
                        <p>在合适的地方启动线程：new MyThread().start();</p>
                        <p>方法2：如果自己的类已经extends另一个类，就无法直接extends Thread，此时，必须实现一个Runnable接口：</p>
                        <p>public class MyThread extends OtherClass implements Runnable {<br>public run() {<br>System.out.println("MyThread.run()");<br>}<br>}</p>
                        <p>为了启动MyThread，需要首先实例化一个Thread，并传入自己的MyThread实例：</p>
                        <p>MyThread myt = new MyThread();<br>Thread t = new Thread(myt);<br>t.start();</p>
                        <p>事实上，当传入一个Runnable target参数给Thread后，Thread的run()方法就会调用target.run()，参考JDK源代码：</p>
                        <p>public void run() {<br>if (target != null) {<br>target.run();<br>}<br>}</p>
                        <p>线程还有一些Name, ThreadGroup, isDaemon等设置，由于和线程设计模式关联很少，这里就不多说了。</p>
                        <p>由于同一进程内的多个线程共享内存空间，在Java中，就是共享实例，当多个线程试图同时修改某个实例的内容时，就会造成冲突，因此，线程必须实现共享互斥，使多线程同步。</p>
                        <p>最简单的同步是将一个方法标记为synchronized，对同一个实例来说，任一时刻只能有一个synchronized方法在执行。当一个方法正在执行某个synchronized方法时，其他线程如果想要执行这个实例的任意一个synchronized方法，都必须等待当前执行 synchronized方法的线程退出此方法后，才能依次执行。</p>
                        <p>但是，非synchronized方法不受影响，不管当前有没有执行synchronized方法，非synchronized方法都可以被多个线程同时执行。</p>
                        <p>此外，必须注意，只有同一实例的synchronized方法同一时间只能被一个线程执行，不同实例的synchronized方法是可以并发的。例如，class A定义了synchronized方法sync()，则不同实例a1.sync()和a2.sync()可以同时由两个线程来执行。</p>
                        <p>多线程同步的实现最终依赖锁机制。我们可以想象某一共享资源是一间屋子，每个人都是一个线程。当A希望进入房间时，他必须获得门锁，一旦A获得门锁，他进去后就立刻将门锁上，于是B,C,D...就不得不在门外等待，直到A释放锁出来后，B,C,D...中的某一人抢到了该锁（具体抢法依赖于 JVM的实现，可以先到先得，也可以随机挑选），然后进屋又将门锁上。这样，任一时刻最多有一人在屋内（使用共享资源）。</p>
                        <p>Java语言规范内置了对多线程的支持。对于Java程序来说，每一个对象实例都有一把&#8220;锁&#8221;，一旦某个线程获得了该锁，别的线程如果希望获得该锁，只能等待这个线程释放锁之后。获得锁的方法只有一个，就是synchronized关键字。例如：</p>
                        <p>public class SharedResource {<br>private int count = 0;</p>
                        <p>public int getCount() { return count; }</p>
                        <p>public synchronized void setCount(int count) { this.count = count; }</p>
                        <p>}</p>
                        <p>同步方法public synchronized void setCount(int count) { this.count = count; } 事实上相当于：</p>
                        <p>public void setCount(int count) {<br>synchronized(this) { // 在此获得this锁<br>this.count = count;<br>} // 在此释放this锁<br>}</p>
                        <p>红色部分表示需要同步的代码段，该区域为&#8220;危险区域&#8221;，如果两个以上的线程同时执行，会引发冲突，因此，要更改SharedResource的内部状态，必须先获得SharedResource实例的锁。</p>
                        <p>退出synchronized块时，线程拥有的锁自动释放，于是，别的线程又可以获取该锁了。</p>
                        <p>为了提高性能，不一定要锁定this，例如，SharedResource有两个独立变化的变量：</p>
                        <p>public class SharedResouce {<br>private int a = 0;<br>private int b = 0;</p>
                        <p>public synchronized void setA(int a) { this.a = a; }</p>
                        <p>public synchronized void setB(int b) { this.b = b; }<br>}</p>
                        <p>若同步整个方法，则setA()的时候无法setB()，setB()时无法setA()。为了提高性能，可以使用不同对象的锁：</p>
                        <p>public class SharedResouce {<br>private int a = 0;<br>private int b = 0;<br>private Object sync_a = new Object();<br>private Object sync_b = new Object();</p>
                        <p>public void setA(int a) {<br>synchronized(sync_a) {<br>this.a = a;<br>}<br>}</p>
                        <p>public synchronized void setB(int b) {<br>synchronized(sync_b) {<br>this.b = b;<br>}<br>}<br>}</p>
                        <p>通常，多线程之间需要协调工作。例如，浏览器的一个显示图片的线程displayThread想要执行显示图片的任务，必须等待下载线程 downloadThread将该图片下载完毕。如果图片还没有下载完，displayThread可以暂停，当downloadThread完成了任务后，再通知displayThread&#8220;图片准备完毕，可以显示了&#8221;，这时，displayThread继续执行。</p>
                        <p>以上逻辑简单的说就是：如果条件不满足，则等待。当条件满足时，等待该条件的线程将被唤醒。在Java中，这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如：</p>
                        <p>synchronized(obj) {<br>while(!condition) {<br>obj.wait();<br>}<br>obj.doSomething();<br>}</p>
                        <p>当线程A获得了obj锁后，发现条件condition不满足，无法继续下一处理，于是线程A就wait()。</p>
                        <p>在另一线程B中，如果B更改了某些条件，使得线程A的condition条件满足了，就可以唤醒线程A：</p>
                        <p>synchronized(obj) {<br>condition = true;<br>obj.notify();<br>}</p>
                        <p>需要注意的概念是：</p>
                        <p># 调用obj的wait(), notify()方法前，必须获得obj锁，也就是必须写在synchronized(obj) {...} 代码段内。</p>
                        <p># 调用obj.wait()后，线程A就释放了obj的锁，否则线程B无法获得obj锁，也就无法在synchronized(obj) {...} 代码段内唤醒A。</p>
                        <p># 当obj.wait()方法返回后，线程A需要再次获得obj锁，才能继续执行。</p>
                        <p># 如果A1,A2,A3都在obj.wait()，则B调用obj.notify()只能唤醒A1,A2,A3中的一个（具体哪一个由JVM决定）。</p>
                        <p># obj.notifyAll()则能全部唤醒A1,A2,A3，但是要继续执行obj.wait()的下一条语句，必须获得obj锁，因此，A1,A2,A3只有一个有机会获得锁继续执行，例如A1，其余的需要等待A1释放obj锁之后才能继续执行。</p>
                        <p># 当B调用obj.notify/notifyAll的时候，B正持有obj锁，因此，A1,A2,A3虽被唤醒，但是仍无法获得obj锁。直到B退出synchronized块，释放obj锁后，A1,A2,A3中的一个才有机会获得锁继续执行。</p>
                        <p>前面讲了wait/notify机制，Thread还有一个sleep()静态方法，它也能使线程暂停一段时间。sleep与wait的不同点是： sleep并不释放锁，并且sleep的暂停和wait暂停是不一样的。obj.wait会使线程进入obj对象的等待集合中并等待唤醒。</p>
                        <p>但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态，从而使线程立刻抛出InterruptedException。</p>
                        <p>如果线程A希望立即结束线程B，则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在 wait/sleep/join，则线程B会立刻抛出InterruptedException，在catch() {} 中直接return即可安全地结束线程。</p>
                        <p>需要注意的是，InterruptedException是线程自己从内部抛出的，并不是interrupt()方法抛出的。对某一线程调用 interrupt()时，如果该线程正在执行普通的代码，那么该线程根本就不会抛出InterruptedException。但是，一旦该线程进入到 wait()/sleep()/join()后，就会立刻抛出InterruptedException。</p>
                        <p>GuardedSuspention模式主要思想是：</p>
                        <p>当条件不满足时，线程等待，直到条件满足时，等待该条件的线程被唤醒。</p>
                        <p>我们设计一个客户端线程和一个服务器线程，客户端线程不断发送请求给服务器线程，服务器线程不断处理请求。当请求队列为空时，服务器线程就必须等待，直到客户端发送了请求。</p>
                        <p>先定义一个请求队列：Queue</p>
                        <p>package com.crackj2ee.thread;</p>
                        <p>import java.util.*;</p>
                        <p>public class Queue {<br>private List queue = new LinkedList();</p>
                        <p>public synchronized Request getRequest() {<br>while(queue.size()==0) {<br>try {<br>this.wait();<br>}<br>catch(InterruptedException ie) {<br>return null;<br>}<br>}<br>return (Request)queue.remove(0);<br>}</p>
                        <p>public synchronized void putRequest(Request request) {<br>queue.add(request);<br>this.notifyAll();<br>}</p>
                        <p>}</p>
                        <p>蓝色部分就是服务器线程的等待条件，而客户端线程在放入了一个request后，就使服务器线程等待条件满足，于是唤醒服务器线程。</p>
                        <p>客户端线程：ClientThread</p>
                        <p>package com.crackj2ee.thread;</p>
                        <p>public class ClientThread extends Thread {<br>private Queue queue;<br>private String clientName;</p>
                        <p>public ClientThread(Queue queue, String clientName) {<br>this.queue = queue;<br>this.clientName = clientName;<br>}</p>
                        <p>public String toString() {<br>return "[ClientThread-" + clientName + "]";<br>}</p>
                        <p>public void run() {<br>for(int i=0; i&lt;100; i++) {<br>Request request = new Request("" + (long)(Math.random()*10000));<br>System.out.println(this + " send request: " + request);<br>queue.putRequest(request);<br>try {<br>Thread.sleep((long)(Math.random() * 10000 + 1000));<br>}<br>catch(InterruptedException ie) {<br>}<br>}<br>System.out.println(this + " shutdown.");<br>}<br>}</p>
                        <p>服务器线程：ServerThread</p>
                        <p>package com.crackj2ee.thread;<br>public class ServerThread extends Thread {<br>private boolean stop = false;<br>private Queue queue;</p>
                        <p>public ServerThread(Queue queue) {<br>this.queue = queue;<br>}</p>
                        <p>public void shutdown() {<br>stop = true;<br>this.interrupt();<br>try {<br>this.join();<br>}<br>catch(InterruptedException ie) {}<br>}</p>
                        <p>public void run() {<br>while(!stop) {<br>Request request = queue.getRequest();<br>System.out.println("[ServerThread] handle request: " + request);<br>try {<br>Thread.sleep(2000);<br>}<br>catch(InterruptedException ie) {}<br>}<br>System.out.println("[ServerThread] shutdown.");<br>}<br>}</p>
                        <p>服务器线程在红色部分可能会阻塞，也就是说，Queue.getRequest是一个阻塞方法。这和java标准库的许多IO方法类似。</p>
                        <p>最后，写一个Main来启动他们：</p>
                        <p>package com.crackj2ee.thread;</p>
                        <p>public class Main {</p>
                        <p>public static void main(String[] args) {<br>Queue queue = new Queue();<br>ServerThread server = new ServerThread(queue);<br>server.start();<br>ClientThread[] clients = new ClientThread[5];<br>for(int i=0; i&lt;clients.length; i++) {<br>clients[i] = new ClientThread(queue, ""+i);<br>clients[i].start();<br>}<br>try {<br>Thread.sleep(100000);<br>}<br>catch(InterruptedException ie) {}<br>server.shutdown();<br>}<br>}</p>
                        <p>我们启动了5个客户端线程和一个服务器线程，运行结果如下：</p>
                        <p>[ClientThread-0] send request: Request-4984<br>[ServerThread] handle request: Request-4984<br>[ClientThread-1] send request: Request-2020<br>[ClientThread-2] send request: Request-8980<br>[ClientThread-3] send request: Request-5044<br>[ClientThread-4] send request: Request-548<br>[ClientThread-4] send request: Request-6832<br>[ServerThread] handle request: Request-2020<br>[ServerThread] handle request: Request-8980<br>[ServerThread] handle request: Request-5044<br>[ServerThread] handle request: Request-548<br>[ClientThread-4] send request: Request-1681<br>[ClientThread-0] send request: Request-7859<br>[ClientThread-3] send request: Request-3926<br>[ServerThread] handle request: Request-6832<br>[ClientThread-2] send request: Request-9906<br>......</p>
                        <p>可以观察到ServerThread处理来自不同客户端的请求。</p>
                        <p>思考</p>
                        <p>Q: 服务器线程的wait条件while(queue.size()==0)能否换成if(queue.size()==0)?</p>
                        <p>A: 在这个例子中可以，因为服务器线程只有一个。但是，如果服务器线程有多个（例如Web应用程序有多个线程处理并发请求，这非常普遍），就会造成严重问题。</p>
                        <p>Q: 能否用sleep(1000)代替wait()?</p>
                        <p>A: 绝对不可以。sleep()不会释放锁，因此sleep期间别的线程根本没有办法调用getRequest()和putRequest()，导致所有相关线程都被阻塞。</p>
                        <p>Q: (Request)queue.remove(0)可以放到synchronized() {}块外面吗？</p>
                        <p>A: 不可以。因为while()是测试queue，remove()是使用queue，两者是一个原子操作，不能放在synchronized外面。</p>
                        <p>总结</p>
                        <p>多线程设计看似简单，实际上必须非常仔细地考虑各种锁定/同步的条件，稍不小心，就可能出错。并且，当线程较少时，很可能发现不了问题，一旦问题出现又难以调试。</p>
                        <p>所幸的是，已有一些被验证过的模式可以供我们使用，我们会继续介绍一些常用的多线程设计模式。</p>
                        <p><br>前面谈了多线程应用程序能极大地改善用户相应。例如对于一个Web应用程序，每当一个用户请求服务器连接时，服务器就可以启动一个新线程为用户服务。</p>
                        <p>然而，创建和销毁线程本身就有一定的开销，如果频繁创建和销毁线程，CPU和内存开销就不可忽略，垃圾收集器还必须负担更多的工作。因此，线程池就是为了避免频繁创建和销毁线程。</p>
                        <p>每当服务器接受了一个新的请求后，服务器就从线程池中挑选一个等待的线程并执行请求处理。处理完毕后，线程并不结束，而是转为阻塞状态再次被放入线程池中。这样就避免了频繁创建和销毁线程。</p>
                        <p>Worker Pattern实现了类似线程池的功能。首先定义Task接口：</p>
                        <p>package com.crackj2ee.thread;<br>public interface Task {<br>void execute();<br>}</p>
                        <p>线程将负责执行execute()方法。注意到任务是由子类通过实现execute()方法实现的，线程本身并不知道自己执行的任务。它只负责运行一个耗时的execute()方法。</p>
                        <p>具体任务由子类实现，我们定义了一个CalculateTask和一个TimerTask：</p>
                        <p>// CalculateTask.java<br>package com.crackj2ee.thread;<br>public class CalculateTask implements Task {<br>private static int count = 0;<br>private int num = count;<br>public CalculateTask() {<br>count++;<br>}<br>public void execute() {<br>System.out.println("[CalculateTask " + num + "] start...");<br>try {<br>Thread.sleep(3000);<br>}<br>catch(InterruptedException ie) {}<br>System.out.println("[CalculateTask " + num + "] done.");<br>}<br>}</p>
                        <p>// TimerTask.java<br>package com.crackj2ee.thread;<br>public class TimerTask implements Task {<br>private static int count = 0;<br>private int num = count;<br>public TimerTask() {<br>count++;<br>}<br>public void execute() {<br>System.out.println("[TimerTask " + num + "] start...");<br>try {<br>Thread.sleep(2000);<br>}<br>catch(InterruptedException ie) {}<br>System.out.println("[TimerTask " + num + "] done.");<br>}<br>}</p>
                        <p>以上任务均简单的sleep若干秒。</p>
                        <p>TaskQueue实现了一个队列，客户端可以将请求放入队列，服务器线程可以从队列中取出任务：</p>
                        <p>package com.crackj2ee.thread;<br>import java.util.*;<br>public class TaskQueue {<br>private List queue = new LinkedList();<br>public synchronized Task getTask() {<br>while(queue.size()==0) {<br>try {<br>this.wait();<br>}<br>catch(InterruptedException ie) {<br>return null;<br>}<br>}<br>return (Task)queue.remove(0);<br>}<br>public synchronized void putTask(Task task) {<br>queue.add(task);<br>this.notifyAll();<br>}<br>}</p>
                        <p>终于到了真正的WorkerThread，这是真正执行任务的服务器线程：</p>
                        <p>package com.crackj2ee.thread;<br>public class WorkerThread extends Thread {<br>private static int count = 0;<br>private boolean busy = false;<br>private boolean stop = false;<br>private TaskQueue queue;<br>public WorkerThread(ThreadGroup group, TaskQueue queue) {<br>super(group, "worker-" + count);<br>count++;<br>this.queue = queue;<br>}<br>public void shutdown() {<br>stop = true;<br>this.interrupt();<br>try {<br>this.join();<br>}<br>catch(InterruptedException ie) {}<br>}<br>public boolean isIdle() {<br>return !busy;<br>}<br>public void run() {<br>System.out.println(getName() + " start."); <br>while(!stop) {<br>Task task = queue.getTask();<br>if(task!=null) {<br>busy = true;<br>task.execute();<br>busy = false;<br>}<br>}<br>System.out.println(getName() + " end.");<br>}<br>}</p>
                        <p>前面已经讲过，queue.getTask()是一个阻塞方法，服务器线程可能在此wait()一段时间。此外，WorkerThread还有一个shutdown方法，用于安全结束线程。</p>
                        <p>最后是ThreadPool，负责管理所有的服务器线程，还可以动态增加和减少线程数：</p>
                        <p>package com.crackj2ee.thread;<br>import java.util.*;<br>public class ThreadPool extends ThreadGroup {<br>private List threads = new LinkedList();<br>private TaskQueue queue;<br>public ThreadPool(TaskQueue queue) {<br>super("Thread-Pool");<br>this.queue = queue;<br>}<br>public synchronized void addWorkerThread() {<br>Thread t = new WorkerThread(this, queue);<br>threads.add(t);<br>t.start();<br>}<br>public synchronized void removeWorkerThread() {<br>if(threads.size()&gt;0) {<br>WorkerThread t = (WorkerThread)threads.remove(0);<br>t.shutdown();<br>}<br>}<br>public synchronized void currentStatus() {<br>System.out.println("-----------------------------------------------");<br>System.out.println("Thread count = " + threads.size());<br>Iterator it = threads.iterator();<br>while(it.hasNext()) {<br>WorkerThread t = (WorkerThread)it.next();<br>System.out.println(t.getName() + ": " + (t.isIdle() ? "idle" : "busy"));<br>}<br>System.out.println("-----------------------------------------------");<br>}<br>}</p>
                        <p>currentStatus()方法是为了方便调试，打印出所有线程的当前状态。</p>
                        <p>最后，Main负责完成main()方法：</p>
                        <p>package com.crackj2ee.thread;<br>public class Main {<br>public static void main(String[] args) {<br>TaskQueue queue = new TaskQueue();<br>ThreadPool pool = new ThreadPool(queue);<br>for(int i=0; i&lt;10; i++) {<br>queue.putTask(new CalculateTask());<br>queue.putTask(new TimerTask());<br>}<br>pool.addWorkerThread();<br>pool.addWorkerThread();<br>doSleep(8000);<br>pool.currentStatus();<br>pool.addWorkerThread();<br>pool.addWorkerThread();<br>pool.addWorkerThread();<br>pool.addWorkerThread();<br>pool.addWorkerThread();<br>doSleep(5000);<br>pool.currentStatus();<br>}<br>private static void doSleep(long ms) {<br>try {<br>Thread.sleep(ms);<br>}<br>catch(InterruptedException ie) {}<br>}<br>}</p>
                        <p>main()一开始放入了20个Task，然后动态添加了一些服务线程，并定期打印线程状态，运行结果如下：</p>
                        <p>worker-0 start.<br>[CalculateTask 0] start...<br>worker-1 start.<br>[TimerTask 0] start...<br>[TimerTask 0] done.<br>[CalculateTask 1] start...<br>[CalculateTask 0] done.<br>[TimerTask 1] start...<br>[CalculateTask 1] done.<br>[CalculateTask 2] start...<br>[TimerTask 1] done.<br>[TimerTask 2] start...<br>[TimerTask 2] done.<br>[CalculateTask 3] start...<br>-----------------------------------------------<br>Thread count = 2<br>worker-0: busy<br>worker-1: busy<br>-----------------------------------------------<br>[CalculateTask 2] done.<br>[TimerTask 3] start...<br>worker-2 start.<br>[CalculateTask 4] start...<br>worker-3 start.<br>[TimerTask 4] start...<br>worker-4 start.<br>[CalculateTask 5] start...<br>worker-5 start.<br>[TimerTask 5] start...<br>worker-6 start.<br>[CalculateTask 6] start...<br>[CalculateTask 3] done.<br>[TimerTask 6] start...<br>[TimerTask 3] done.<br>[CalculateTask 7] start...<br>[TimerTask 4] done.<br>[TimerTask 7] start...<br>[TimerTask 5] done.<br>[CalculateTask 8] start...<br>[CalculateTask 4] done.<br>[TimerTask 8] start...<br>[CalculateTask 5] done.<br>[CalculateTask 9] start...<br>[CalculateTask 6] done.<br>[TimerTask 9] start...<br>[TimerTask 6] done.<br>[TimerTask 7] done.<br>-----------------------------------------------<br>Thread count = 7<br>worker-0: idle<br>worker-1: busy<br>worker-2: busy<br>worker-3: idle<br>worker-4: busy<br>worker-5: busy<br>worker-6: busy<br>-----------------------------------------------<br>[CalculateTask 7] done.<br>[CalculateTask 8] done.<br>[TimerTask 8] done.<br>[TimerTask 9] done.<br>[CalculateTask 9] done.</p>
                        <p>仔细观察：一开始只有两个服务器线程，因此线程状态都是忙，后来线程数增多，6个线程中的两个状态变成idle，说明处于wait()状态。</p>
                        <p>思考：本例的线程调度算法其实根本没有，因为这个应用是围绕TaskQueue设计的，不是以Thread Pool为中心设计的。因此，Task调度取决于TaskQueue的getTask()方法，你可以改进这个方法，例如使用优先队列，使优先级高的任务先被执行。</p>
                        <p>如果所有的服务器线程都处于busy状态，则说明任务繁忙，TaskQueue的队列越来越长，最终会导致服务器内存耗尽。因此，可以限制 TaskQueue的等待任务数，超过最大长度就拒绝处理。许多Web服务器在用户请求繁忙时就会拒绝用户：HTTP 503 SERVICE UNAVAILABLE</p>
                        <p>多线程读写同一个对象的数据是很普遍的，通常，要避免读写冲突，必须保证任何时候仅有一个线程在写入，有线程正在读取的时候，写入操作就必须等待。简单说，就是要避免&#8220;写-写&#8221;冲突和&#8220;读-写&#8221;冲突。但是同时读是允许的，因为&#8220;读-读&#8221;不冲突，而且很安全。</p>
                        <p>要实现以上的ReadWriteLock，简单的使用synchronized就不行，我们必须自己设计一个ReadWriteLock类，在读之前，必须先获得&#8220;读锁&#8221;，写之前，必须先获得&#8220;写锁&#8221;。举例说明：</p>
                        <p>DataHandler对象保存了一个可读写的char[]数组：</p>
                        <p>package com.crackj2ee.thread;</p>
                        <p>public class DataHandler {<br>// store data:<br>private char[] buffer = "AAAAAAAAAA".toCharArray();</p>
                        <p>private char[] doRead() {<br>char[] ret = new char[buffer.length];<br>for(int i=0; i&lt;buffer.length; i++) {<br>ret[i] = buffer[i];<br>sleep(3);<br>}<br>return ret;<br>}</p>
                        <p>private void doWrite(char[] data) {<br>if(data!=null) {<br>buffer = new char[data.length];<br>for(int i=0; i&lt;buffer.length; i++) {<br>buffer[i] = data[i];<br>sleep(10);<br>}<br>}<br>}</p>
                        <p>private void sleep(int ms) {<br>try {<br>Thread.sleep(ms);<br>}<br>catch(InterruptedException ie) {}<br>}<br>}</p>
                        <p>doRead()和doWrite()方法是非线程安全的读写方法。为了演示，加入了sleep()，并设置读的速度大约是写的3倍，这符合通常的情况。</p>
                        <p>为了让多线程能安全读写，我们设计了一个ReadWriteLock：</p>
                        <p>package com.crackj2ee.thread;<br>public class ReadWriteLock {<br>private int readingThreads = 0;<br>private int writingThreads = 0;<br>private int waitingThreads = 0; // waiting for write<br>private boolean preferWrite = true;</p>
                        <p>public synchronized void readLock() throws InterruptedException {<br>while(writingThreads&gt;0 || (preferWrite &amp;&amp; waitingThreads&gt;0))<br>this.wait();<br>readingThreads++;<br>}</p>
                        <p>public synchronized void readUnlock() {<br>readingThreads--;<br>preferWrite = true;<br>notifyAll();<br>}</p>
                        <p>public synchronized void writeLock() throws InterruptedException {<br>waitingThreads++;<br>try {<br>while(readingThreads&gt;0 || writingThreads&gt;0)<br>this.wait();<br>}<br>finally {<br>waitingThreads--;<br>}<br>writingThreads++;<br>}</p>
                        <p>public synchronized void writeUnlock() {<br>writingThreads--;<br>preferWrite = false;<br>notifyAll();<br>}<br>}</p>
                        <p>readLock()用于获得读锁，readUnlock()释放读锁，writeLock()和writeUnlock()一样。由于锁用完必须释放，因此，必须保证lock和unlock匹配。我们修改DataHandler，加入ReadWriteLock：</p>
                        <p>package com.crackj2ee.thread;<br>public class DataHandler {<br>// store data:<br>private char[] buffer = "AAAAAAAAAA".toCharArray();<br>// lock:<br>private ReadWriteLock lock = new ReadWriteLock();</p>
                        <p>public char[] read(String name) throws InterruptedException {<br>System.out.println(name + " waiting for read...");<br>lock.readLock();<br>try {<br>char[] data = doRead();<br>System.out.println(name + " reads data: " + new String(data));<br>return data;<br>}<br>finally {<br>lock.readUnlock();<br>}<br>}</p>
                        <p>public void write(String name, char[] data) throws InterruptedException {<br>System.out.println(name + " waiting for write...");<br>lock.writeLock();<br>try {<br>System.out.println(name + " wrote data: " + new String(data));<br>doWrite(data);<br>}<br>finally {<br>lock.writeUnlock();<br>}<br>}</p>
                        <p>private char[] doRead() {<br>char[] ret = new char[buffer.length];<br>for(int i=0; i&lt;buffer.length; i++) {<br>ret[i] = buffer[i];<br>sleep(3);<br>}<br>return ret;<br>}<br>private void doWrite(char[] data) {<br>if(data!=null) {<br>buffer = new char[data.length];<br>for(int i=0; i&lt;buffer.length; i++) {<br>buffer[i] = data[i];<br>sleep(10);<br>}<br>}<br>}<br>private void sleep(int ms) {<br>try {<br>Thread.sleep(ms);<br>}<br>catch(InterruptedException ie) {}<br>}<br>}</p>
                        <p>public方法read()和write()完全封装了底层的ReadWriteLock，因此，多线程可以安全地调用这两个方法：</p>
                        <p>// ReadingThread不断读取数据：<br>package com.crackj2ee.thread;<br>public class ReadingThread extends Thread {<br>private DataHandler handler;<br>public ReadingThread(DataHandler handler) {<br>this.handler = handler;<br>}<br>public void run() {<br>for(;;) {<br>try {<br>char[] data = handler.read(getName());<br>Thread.sleep((long)(Math.random()*1000+100));<br>}<br>catch(InterruptedException ie) {<br>break;<br>}<br>}<br>}<br>}</p>
                        <p>// WritingThread不断写入数据，每次写入的都是10个相同的字符：<br>package com.crackj2ee.thread;<br>public class WritingThread extends Thread {<br>private DataHandler handler;<br>public WritingThread(DataHandler handler) {<br>this.handler = handler;<br>}<br>public void run() {<br>char[] data = new char[10];<br>for(;;) {<br>try {<br>fill(data);<br>handler.write(getName(), data);<br>Thread.sleep((long)(Math.random()*1000+100));<br>}<br>catch(InterruptedException ie) {<br>break;<br>}<br>}<br>}<br>// 产生一个A-Z随机字符，填入char[10]:<br>private void fill(char[] data) {<br>char c = (char)(Math.random()*26+'A');<br>for(int i=0; i&lt;data.length; i++)<br>data[i] = c;<br>}<br>}</p>
                        <p>最后Main负责启动这些线程：</p>
                        <p>package com.crackj2ee.thread;<br>public class Main {<br>public static void main(String[] args) {<br>DataHandler handler = new DataHandler();<br>Thread[] ts = new Thread[] {<br>new ReadingThread(handler),<br>new ReadingThread(handler),<br>new ReadingThread(handler),<br>new ReadingThread(handler),<br>new ReadingThread(handler),<br>new WritingThread(handler),<br>new WritingThread(handler)<br>};<br>for(int i=0; i&lt;ts.length; i++) {<br>ts[i].start();<br>}<br>}<br>}</p>
                        <p>我们启动了5个读线程和2个写线程，运行结果如下：</p>
                        <p>Thread-0 waiting for read...<br>Thread-1 waiting for read...<br>Thread-2 waiting for read...<br>Thread-3 waiting for read...<br>Thread-4 waiting for read...<br>Thread-5 waiting for write...<br>Thread-6 waiting for write...<br>Thread-4 reads data: AAAAAAAAAA<br>Thread-3 reads data: AAAAAAAAAA<br>Thread-2 reads data: AAAAAAAAAA<br>Thread-1 reads data: AAAAAAAAAA<br>Thread-0 reads data: AAAAAAAAAA<br>Thread-5 wrote data: EEEEEEEEEE<br>Thread-6 wrote data: MMMMMMMMMM<br>Thread-1 waiting for read...<br>Thread-4 waiting for read...<br>Thread-1 reads data: MMMMMMMMMM<br>Thread-4 reads data: MMMMMMMMMM<br>Thread-2 waiting for read...<br>Thread-2 reads data: MMMMMMMMMM<br>Thread-0 waiting for read...<br>Thread-0 reads data: MMMMMMMMMM<br>Thread-4 waiting for read...<br>Thread-4 reads data: MMMMMMMMMM<br>Thread-2 waiting for read...<br>Thread-5 waiting for write...<br>Thread-2 reads data: MMMMMMMMMM<br>Thread-5 wrote data: GGGGGGGGGG<br>Thread-6 waiting for write...<br>Thread-6 wrote data: AAAAAAAAAA<br>Thread-3 waiting for read...<br>Thread-3 reads data: AAAAAAAAAA<br>......</p>
                        <p>可以看到，每次读/写都是完整的原子操作，因为我们每次写入的都是10个相同字符。并且，每次读出的都是最近一次写入的内容。</p>
                        <p>如果去掉ReadWriteLock：</p>
                        <p>package com.crackj2ee.thread;<br>public class DataHandler {</p>
                        <p>// store data:<br>private char[] buffer = "AAAAAAAAAA".toCharArray();</p>
                        <p>public char[] read(String name) throws InterruptedException {<br>char[] data = doRead();<br>System.out.println(name + " reads data: " + new String(data));<br>return data;<br>}<br>public void write(String name, char[] data) throws InterruptedException {<br>System.out.println(name + " wrote data: " + new String(data));<br>doWrite(data);<br>}</p>
                        <p>private char[] doRead() {<br>char[] ret = new char[10];<br>for(int i=0; i&lt;10; i++) {<br>ret[i] = buffer[i];<br>sleep(3);<br>}<br>return ret;<br>}<br>private void doWrite(char[] data) {<br>for(int i=0; i&lt;10; i++) {<br>buffer[i] = data[i];<br>sleep(10);<br>}<br>}<br>private void sleep(int ms) {<br>try {<br>Thread.sleep(ms);<br>}<br>catch(InterruptedException ie) {}<br>}<br>}</p>
                        <p>运行结果如下：</p>
                        <p>Thread-5 wrote data: AAAAAAAAAA<br>Thread-6 wrote data: MMMMMMMMMM<br>Thread-0 reads data: AAAAAAAAAA<br>Thread-1 reads data: AAAAAAAAAA<br>Thread-2 reads data: AAAAAAAAAA<br>Thread-3 reads data: AAAAAAAAAA<br>Thread-4 reads data: AAAAAAAAAA<br>Thread-2 reads data: MAAAAAAAAA<br>Thread-3 reads data: MAAAAAAAAA<br>Thread-5 wrote data: CCCCCCCCCC<br>Thread-1 reads data: MAAAAAAAAA<br>Thread-0 reads data: MAAAAAAAAA<br>Thread-4 reads data: MAAAAAAAAA<br>Thread-6 wrote data: EEEEEEEEEE<br>Thread-3 reads data: EEEEECCCCC<br>Thread-4 reads data: EEEEEEEEEC<br>Thread-1 reads data: EEEEEEEEEE</p>
                        <p>可以看到在Thread-6写入EEEEEEEEEE的过程中，3个线程读取的内容是不同的。</p>
                        <p>思考</p>
                        <p>java的synchronized提供了最底层的物理锁，要在synchronized的基础上，实现自己的逻辑锁，就必须仔细设计ReadWriteLock。</p>
                        <p>Q: lock.readLock()为什么不放入try{ } 内？<br>A: 因为readLock()会抛出InterruptedException，导致readingThreads++不执行，而readUnlock()在 finally{ } 中，导致readingThreads--执行，从而使readingThread状态出错。writeLock()也是类似的。</p>
                        <p>Q: preferWrite有用吗？<br>A: 如果去掉preferWrite，线程安全不受影响。但是，如果读取线程很多，上一个线程还没有读取完，下一个线程又开始读了，就导致写入线程长时间无法获得writeLock；如果写入线程等待的很多，一个接一个写，也会导致读取线程长时间无法获得readLock。preferWrite的作用是让读 /写交替执行，避免由于读线程繁忙导致写无法进行和由于写线程繁忙导致读无法进行。</p>
                        <p>Q: notifyAll()换成notify()行不行？<br>A: 不可以。由于preferWrite的存在，如果一个线程刚读取完毕，此时preferWrite=true，再notify()，若恰好唤醒的是一个读线程，则while(writingThreads&gt;0 || (preferWrite &amp;&amp; waitingThreads&gt;0))可能为true导致该读线程继续等待，而等待写入的线程也处于wait()中，结果所有线程都处于wait ()状态，谁也无法唤醒谁。因此，notifyAll()比notify()要来得安全。程序验证notify()带来的死锁：</p>
                        <p>Thread-0 waiting for read...<br>Thread-1 waiting for read...<br>Thread-2 waiting for read...<br>Thread-3 waiting for read...<br>Thread-4 waiting for read...<br>Thread-5 waiting for write...<br>Thread-6 waiting for write...<br>Thread-0 reads data: AAAAAAAAAA<br>Thread-4 reads data: AAAAAAAAAA<br>Thread-3 reads data: AAAAAAAAAA<br>Thread-2 reads data: AAAAAAAAAA<br>Thread-1 reads data: AAAAAAAAAA<br>Thread-5 wrote data: CCCCCCCCCC<br>Thread-2 waiting for read...<br>Thread-1 waiting for read...<br>Thread-3 waiting for read...<br>Thread-0 waiting for read...<br>Thread-4 waiting for read...<br>Thread-6 wrote data: LLLLLLLLLL<br>Thread-5 waiting for write...<br>Thread-6 waiting for write...<br>Thread-2 reads data: LLLLLLLLLL<br>Thread-2 waiting for read...<br>（运行到此不动了）</p>
                        <p>注意到这种死锁是由于所有线程都在等待别的线程唤醒自己，结果都无法醒过来。这和两个线程希望获得对方已有的锁造成死锁不同。因此多线程设计的难度远远高于单线程应用。</p>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.blogjava.net/xixidabao/aggbug/53641.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-06-18 21:38 <a href="http://www.blogjava.net/xixidabao/archive/2006/06/18/53641.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>再论Java Swing线程</title><link>http://www.blogjava.net/xixidabao/archive/2006/05/20/47235.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Sat, 20 May 2006 15:59:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/05/20/47235.html</guid><description><![CDATA[
		<font size="+0">
				<pre>　　不正确的Swing线程是运行缓慢、无响应和不稳定的Swing应用的主要原因之一。这是许多原因造成的，从开发人员对Swing单线程模型的误解，到保证正确的线程执行的困难。即使对Swing线程进行了很多努力，应用线程逻辑也是很难理解和维护的。本文阐述了如何在开发Swing应用中使用事件驱动编程，以大大简化开发、维护，并提供高灵活性。 

　　背景

　　既然我们是要简化Swing应用的线程，首先让我们来看看Swing线程是怎么工作的，为什么它是必须的。Swing API是围绕单线程模型设计的。这意味着Swing组件必须总是通过同一个线程来修改和操纵。为什么采用单线程模型，这有很多原因，包括开发成本和同步Swing的复杂性－－这都会造成一个迟钝的API。为了达到单线程模型，有一个专门的线程用于和Swing组件交互。这个线程就是大家熟知的Swing线程，AWT（有时也发音为“ought”）线程，或者事件分派线程。在本文的下面的部分，我选用Swing线程的叫法。
既然Swing线程是和Swing组件进行交互的唯一的线程，它就被赋予了很多责任。所有的绘制和图形，鼠标事件，组件事件，按钮事件，和所有其它事件都发生在Swing线程。因为Swing线程的工作已经非常沉重了，当太多其它工作在Swing线程中进行处理时就会发生问题。会引起这个问题的最常见的位置是在非Swing处理的地方，像发生在一个事件监听器方法中，比如JButton的ActionListener，的数据库查找。既然ActionListener的actionPerformed()方法自动在Swing线程中执行，那么，数据库查找也将在Swing线程中执行。这将占用了Swing的工作，阻止它处理它的其它任务－－像绘制，响应鼠标移动，处理按钮事件，和应用的缩放。用户以为应用死掉了，但实际上并不是这样。在适当的线程中执行代码对确保系统正常地执行非常重要。 

　　既然我们已经看到了在适当的线程中执行Swing应用的代码是多么重要，现在让我们如何实现这些线程。我们看看将代码放入和移出Swing线程的标准机制。在讲述过程中，我将突出几个和标准机制有关的问题和难点。正如我们看到的，大部分的问题都来自于企图在异步的Swing线程模型上实现同步的代码模型。从那儿，我们将看到如何修改我们的例子到事件驱动－－移植整个方式到异步模型。

</pre>
				<br />
				<br />
				<br />
				<b>通用Swing线程解决方案</b>
				<br />
				<br />　　让我们以一个最常用的Swing线程错误开始。我们将企图使用标准的技术来修正这个问题。在这个过程中，我们将看到实现正确的Swing线程的复杂性和常见困难。并且，注意在修正这个Swing线程问题中，许多中间的例子也是不能工作的。在例子中，我在代码失败的地方以//broken开头标出。好了，现在，让我们进入我们的例子吧。<br /><br />　　假设我们在执行图书查找。我们有一个简单的用户界面，包括一个查找文本域，一个查找按钮，和一个输出的文本区域。这个接口如图1所示。不要批评我的UI设计，这个确实很丑陋，我承认。<br /><br /><img onerror="this.src='http://www.yesky.com/image20010518/82485.jpg';" hspace="3" src="/image20010518/82485.jpg" align="center" vspace="1" border="1" /><br />图 1. 基本查询用户界面<br /><br />　　用户输入书的标题，作者或者其它条件，然后显示一个结果的列表。下面的代码例子演示了按钮的ActionListener在同一个线程中调用lookup()方法。在这些例子中，我使用了thread.sleep()休眠5秒来作为一个占位的外部查找。线程休眠的结果等同于一个耗时5秒的同步的服务器调用。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>private void searchButton_actionPerformed() <br />{ <br />　outputTA.setText("Searching for: " + searchTF.getText()); <br />　//Broken!! Too much work in the Swing <br />　thread String[] results = lookup(searchTF.getText());<br />　outputTA.setText(""); <br />　for (int i = 0; i &lt; results.length; i++) <br />　{<br />　　String result = results[i];<br />　　outputTA.setText(outputTA.getText() + '\n' + result); <br />　 }<br />}</td></tr></tbody></table><br />　　如果你运行这段代码（完整的代码可以在这儿下载），你会立即发现存在一些问题。图2显示了查找运行中的一个屏幕截图。<br /><br /><img onerror="this.src='http://www.yesky.com/image20010518/82486.jpg';" hspace="3" src="/image20010518/82486.jpg" align="center" vspace="1" border="1" /><br />图 2. 在Swing线程中进行查找<br /><br />　　注意Go按钮看起来是被按下了。这是因为actionPerformed方法通知了按钮绘制为非按下外观，但是还没有返回。你也会发现要查找的字串“abcde”并没有出现在文本区域中。searchButton_actionPerformed的第1行代码将文本区域设置为要查找的字串。但是，注意Swing重画并不是立即执行的。而是把重画请求放置到Swing事件队列中等待Swing线程处理。但是这儿，我们因查找处理占用了Swing线程，所以，它还不能马上进行重画。<br /><br />　　要修正这些问题，让我们把查找操作移入非Swing线程中。我们第一个想到的就是让整个方法在一个新的线程中执行。这样作的问题是Swing组件，本例中的文本区域，只能从Swing线程中进行编辑。下面是修改后的searchButton_actionPerformed方法：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>private void searchButton_actionPerformed() <br />{<br />　outputTA.setText("Searching for: " + searchTF.getText()); <br />　//the String[][] is used to allow access to<br />　// setting the results from an inner class <br />　final String[][] results = new String[1][1]; <br />　new Thread()<br />　{<br />　　public void run()<br />　　{<br />　　　results[0] = lookup(searchTF.getText());<br />　　 }<br />　 }.start();<br />　outputTA.setText("");<br />　for (int i = 0; i &lt; results[0].length; i++) <br />　 {<br />　　String result = results[0][i];<br />　　outputTA.setText(outputTA.getText() + '\n' + result); <br />　 }<br />}</td></tr></tbody></table><br />　　这种方法有很多问题。注意final String[][] 。这是一个处理匿名内部类和作用域的不得已的替代。基本上，在匿名内部类中使用的，但在外部环绕类作用域中定义的任何变量都需要定义为final。你可以通过创建一个数组来持有变量解决这个问题。这样的话，你可以创建数组为final的，修改数组中的元素，而不是数组的引用自身。既然我们已经解决这个问题，让我们进入真正的问题所在吧。图3显示了这段代码运行时发生的情况：<br /><br /><img onerror="this.src='http://www.yesky.com/image20010518/82487.jpg';" hspace="3" src="/image20010518/82487.jpg" align="center" vspace="1" border="1" /><br />图 3. 在Swing线程外部进行查找<br /><br />　　界面显示了一个null，因为显示代码在查找代码完成前被处理了。这是因为一旦新的线程启动了，代码块继续执行，而不是等待线程执行完毕。这是那些奇怪的并发代码块中的一个，下面将把它编写到一个方法中使其能够真正执行。<br /><br />　　在SwingUtilities类中有两个方法可以帮助我们解决这些问题：invokerLater()和invokeAndWait()。每一个方法都以一个Runnable作为参数，并在Swing线程中执行它。invokeAndWait（）方法阻塞直到Runnnable执行完毕；invokeLater()异步地执行Runnable。invokeAndWait()一般不赞成使用，因为它可能导致严重的线程死锁，对你的应用造成严重的破坏。所以，让我们把它放置一边，使用invokeLater()方法。<br />要修正最后一个变量变量scooping和执行顺序的问题，我们必须将文本区域的getText()和setText()方法调用移入一个Runnable，只有在查询结果返回后再执行它，并且在Swing线程中执行。我们可以这样作，创建一个匿名Runnable传递给invokeLater()，包括在新线程的Runnable后的文本区域操作。这保证了Swing代码不会在查找结束之前执行。下面是修正后的代码：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>private void searchButton_actionPerformed()<br />{<br />　outputTA.setText("Searching for: " + searchTF.getText()); <br />　final String[][] results = new String[1][1];<br />　new Thread()<br />　{<br />　　public void run() <br />　　{ //get results.<br />　　　results[0] = lookup(searchTF.getText())<br />　　　// send runnable to the Swing thread<br />　　　// the runnable is queued after the<br />　　　// results are returned <br />　　　SwingUtilities.invokeLater( <br />　　　　new Runnable() <br />　　　　{<br />　　　　　public void run() <br />　　　　　{<br />　　　　　　// Now we're in the Swing thread<br />　　　　　　outputTA.setText(""); <br />　　　　　　for (int i = 0; i &lt; results[0].length; i++)<br />　　　　　　{<br />　　　　　　　String result = results[0][i];<br />　　　　　　　outputTA.setText( outputTA.getText() + '\n' + result);<br />　　　　　　 } <br />　　　　　 }<br />　　　　}<br />　　　);<br />　　}<br />　}.start();}</td></tr></tbody></table><br />　　这可以工作，但是这样做令人非常头痛。我们不得不对通过匿名线程执行的顺序，我们还不得不处理困难的scooping问题。问题并不少见，并且，这只是一个非常简单的例子，我们已经遇到了作用域，变量传递，和执行顺序等一系列问题。相像一个更复杂的问题，包含了几层嵌套，共享的引用和指定的执行顺序。这种方法很快就失控了。<br /><br /><pre>问题

　　我们在企图强制通过异步模型进行同步执行－－企图将一个方形的螺栓放到一个圆形的空中。只有我们尝试这样做，我们就会不断地遭遇这些问题。从我的经验，可以告诉你这些代码很难阅读，很难维护，并且易于出错。

　　这看起来是一个常见的问题，所以一定有标准的方式来解决，对吗？出现了一些框架用于管理Swing的复杂性，所以让我们来快速预览一下它们可以做什么。

　　一个可以得到的解决方案是Foxtrot，一个由Biorn Steedom写的框架，可以在SourceForge上获取。它使用一个叫做Worker的对象来控制非Swing任务在非Swing线程中的执行，阻塞直到非Swing任务执行完毕。它简化了Swing线程，允许你编写同步代码，并在Swing线程和非Swing线程直接切换。下面是来自它的站点的一个例子：

public void actionPerformed(ActionEvent e)
{
　button.setText("Sleeping..."); 
　String text = null; 
　try 
　{
　　text = (String)Worker.post(new Task() {
　　　public Object run() throws Exception { 
　　　　Thread.sleep(10000); return "Slept !";
　　　　} 
　　　}
　　);
　}
　catch (Exception x) ... button.setText(text); somethingElse();} 

　　注意它是如何解决上面的那些问题的。我们能够非常容易地在Swing线程中传入传出变量。并且，代码块看起来也很正确－－先编写的先执行。但是仍然有一些问题障碍阻止使用从准同步异步解决方案。Foxtrot中的一个问题是异常管理。使用Foxtrot，每次调用Worker必须捕获Exception。这是将执行代理给Worker来解决同步对异步问题的一个产物。

　　同样以非常相似的方式，我此前也创建了一个框架，我称它为链接运行引擎（Chained Runnable Engine） ，同样也遭受来自类似同步对异步问题的困扰。使用这个框架，你将创建一个将被引擎执行的Runnable的集合。每一个Runnable都有一个指示器告诉引擎是否应该在Swing线程或者另外的线程中执行。引擎也保证Runnable以正确的顺序执行。所以Runnable #2将不会放入队列直到Runnable #1执行完毕。并且，它支持变量以HashMap的形式从Runnable到Runnable传递。

　　表面上，它看起来解决了我们的主要问题。但是当你深入进去后，同样的问题又冒出来了。本质上，我们并没有改变上面描述的任何东西－－我们只是将复杂性隐藏在引擎的后面。因为指数级增长的Runnable而使代码编写将变得非常枯燥，也很复杂，并且这些Runnable常常相互耦合。Runnable之间的非类型的HashMap变量传递变得难于管理。问题的列表还有很多。

　　在编写这个框架之后，我意识到这需要一个完全不同的解决方案。这让我重新审视了问题，看别人是怎么解决类似的问题的，并深入的研究了Swing的源代码。

</pre><br /><br /><br /><pre>解决方案：事件驱动编程

　　所有前面的这些解决方案都存在一个共同的致命缺陷－－企图在持续地改变线程的同时表示一个任务的功能集。但是改变线程需要异步的模型，而线程异步地处理Runnable。问题的部分原因是我们在企图在一个异步的线程模型之上实现一个同步的模型。这是所有Runnable之间的链和依赖，执行顺序和内部类scooping问题的根源。如果我们可以构建真正的异步，我们就可以解决我们的问题并极大地简化Swing线程。
在这之前，让我们先列举一下我们要解决的问题：

　　1. 在适当的线程中执行代码 

　　2. 使用SwingUtilities.invokeLater()异步地执行. 

　　异步地执行导致了下面的问题：

　　1. 互相耦合的组件 

　　2. 变量传递的困难 

　　3. 执行的顺序 

　　让我们考虑一下像Java消息服务（JMS）这样的基于消息的系统，因为它们提供了在异步环境中功能组件之间的松散耦合。消息系统触发异步事件，正如在Enterprise Integration Patterns 中描述的。感兴趣的参与者监听该事件，并对事件做成响应－－通常通过执行它们自己的一些代码。结果是一组模块化的，松散耦合的组件，组件可以添加到或者从系统中去除而不影响到其它组件。更重要的，组件之间的依赖被最小化了，而每一个组件都是良好定义的和封装的－－每一个都仅对自己的工作负责。它们简单地触发消息，其它一些组件将响应这个消息，并对其它组件触发的消息进行响应。

　　现在，我们先忽略线程问题，将组件解耦并移植到异步环境中。在我们解决了异步问题后，我们将回过头来看看线程问题。正如我们所将要看到的，那时解决这个问题将非常容易。

　　让我们还拿前面引入的例子，并把它移植到基于事件的模型。首先，我们把lookup调用抽象到一个叫LookupManager的类中。这将允许我们将所有UI类中的数据库逻辑移出，并最终允许我们完全将这两者脱耦。下面是LookupManager类的代码：

class LookupManager { 
　private String[] lookup(String text) {
　　String[] results = ... // database lookup code return results 
　}
} 

　　现在我们开始向异步模型转换。为了使这个调用异步化，我们需要抽象调用的返回。换句话，方法不能返回任何值。我们将以分辨什么相关的动作是其它类所希望知道的开始。在我们这个例子中最明显的事件是搜索结束事件。所以让我们创建一个监听器接口来响应这些事件。该接口含有单个方法lookupCompleted()。下面是接口的定义：

interface LookupListener { public void lookupCompleted(Iterator results);} 

　　遵守Java的标准，我们创建另外一个称作LookupEvent的类包含结果字串数组，而不是到处直接传递字串数组。这将允许我们在不改变LookupListener接口的情况下传递其它信息。例如，我们可以在LookupEvent中同时包括查找的字串和结果。下面是LookupEvent类：

public class LookupEvent {
　String searchText; 
　String[] results;
　public LookupEvent(String searchText) { 
　　this.searchText = searchText;
　}
　public LookupEvent(String searchText, String[] results) { 
　　this.searchText = searchText;
　　this.results = results; 
　}
　public String getSearchText() {
　　return searchText; 
　}
　public String[] getResults() {
　　return results; 
　}
} 

　　注意LookupEvent类是不可变的。这是很重要的，因为我们并不知道在传递过程中谁将处理这些事件。除非我们创建事件的保护拷贝来传递给每一个监听者，我们需要把事件做成不可变的。如果不这样，一个监听者可能会无意或者恶意地修订事件对象，并破坏系统。

　　现在我们需要在LookupManager上调用lookupComplete()事件。我们首先要在LookupManager上添加一个LookupListener的集合：

List listeners = new ArrayList(); 

　　并提供在LookupManager上添加和去除LookupListener的方法：

public void addLookupListener(LookupListener listener){ 
　listeners.add(listener);
}
public void removeLookupListener(LookupListener listener){
　listeners.remove(listener);
} 

　　当动作发生时，我们需要调用监听者的代码。在我们的例子中，我们将在查找返回时触发一个lookupCompleted()事件。这意味着在监听者集合上迭代，并使用一个LookupEvent事件对象调用它们的lookupCompleted()方法。
</pre><br /><br /><br /><pre>我喜欢把这些代码析取到一个独立的方法fire[event-method-name] ，其中构造一个事件对象，在监听器集合上迭代，并调用每一个监听器上的适当的方法。这有助于隔离主要逻辑代码和调用监听器的代码。下面是我们的fireLookupCompleted方法：

private void fireLookupCompleted(String searchText, String[] results){
　LookupEvent event = new LookupEvent(searchText, results); 
　Iterator iter = new ArrayList(listeners).iterator();
　while (iter.hasNext()) {
　　LookupListener listener = (LookupListener) iter.next();
　　listener.lookupCompleted(event); 
　}
} 

　　第2行代码创建了一个新的集合，传入原监听器集合。这在监听器响应事件后决定在LookupManager中去除自己时将发挥作用。如果我们不是安全地拷贝集合，在一些监听器应该 被调用而没有被调用时发生令人厌烦的错误。

　　下面，我们将在动作完成时调用fireLookupCompleted辅助方法。这是lookup方法的返回查询结果的结束处。所以我们可以改变lookup方法使其触发一个事件而不是返回字串数组本身。下面是新的lookup方法：

public void lookup(String text) { 
　//mimic the server call delay... 
　try { 
　　Thread.sleep(5000);
　} catch (Exception e){ 
　　e.printStackTrace(); 
　}
　//imagine we got this from a server 
　String[] results = new String[]{"Book one", "Book two", "Book three"}; 
　fireLookupCompleted(text, results);
} 

　　现在让我们把监听器添加到LookupManager。我们希望当查找返回时更新文本区域。以前，我们只是直接调用setText()方法。因为文本区域是和数据库调用一起都在UI中执行的。既然我们已经将查找逻辑从UI中抽象出来了，我们将把UI类作为一个到LookupManager的监听器，监听lookup事件并相应地更新自己。首先我们将在类定义中实现监听器接口：

public class FixedFrame implements LookupListener 

　　接着我们实现接口方法：

public void lookupCompleted(final LookupEvent e) {
　outputTA.setText("");
　String[] results = e.getResults(); 
　for (int i = 0; i &lt; results.length; i++) {
　　String result = results[i]; 
　　outputTA.setText(outputTA.getText() + "\n" + result); 
　 }
} 

　　最后，我们将它注册为LookupManager的一个监听器：

public FixedFrame() { 
　lookupManager = new LookupManager(); 
　//here we register the listener 
　lookupManager.addListener(this);
　initComponents(); 
　layoutComponents();} 

　　为了简化，我在类的构造器中将它添加为监听器。这在大多数系统上都允许良好。当系统变得更加复杂时，你可能会重构、从构造器中提炼出监听器注册代码，以允许更大的灵活性和扩展性。

　　到现在为止，你看到了所有组件之间的连接，注意职责的分离。用户界面类负责信息的显示－－并且仅负责信息的显示。另一方面，LookupManager类负责所有的lookup连接和逻辑。并且，LookupManager负责在它变化时通知监听器－－而不是当变化发生时应该具体做什么。这允许你连接任意多的监听器。

　　为了演示如何添加新的事件，让我们回头添加一个lookup开始的事件。我们可以添加一个称作lookupStarted()的事件到LookupListener，我们将在查找开始执行前触发它。我们也创建一个fireLookupStarted()事件调用所有LookupListener的lookupStarted()。现在lookup方法如下：

public void lookup(String text) { 
　　fireLookupStarted(text); 
　　//mimic the server call delay... 
　　try { 
　　　Thread.sleep(5000);
　　} catch (Exception e){ 
　　　 e.printStackTrace(); 
　　}
　　//imagine we got this from a server 
　　String[] results = new String[]{"Book one", "Book two", "Book three"}; 
　　fireLookupCompleted(text, results);} 

　　我们也添加新的触发方法fireLookupStarted()。这个方法等同于fireLookupCompleted()方法，除了我们调用监听器上的lookupStarted()方法，并且该事件也不包含结果集。下面是代码：

private void fireLookupStarted(String searchText){
　LookupEvent event = new LookupEvent(searchText); 
　Iterator iter = new ArrayList(listeners).iterator();
　while (iter.hasNext()) { 
　　LookupListener listener = (LookupListener) iter.next();
　　listener.lookupStarted(event);
　}
} 

　　最后，我们在UI类上实现lookupStarted()方法，设置文本区域提示当前搜索的字符串。

public void lookupStarted(final LookupEvent e) {
　outputTA.setText("Searching for: " + e.getSearchText());
} 

　　这个例子展示了添加新的事件是多么容易。现在，让我们看看展示事件驱动脱耦的灵活性。我们将通过创建一个日志类，当一个搜索开始和结束时在命令行中输出信息来演示。我们称这个类为Logger。下面是它的代码：

public class Logger implements LookupListener { 
　public void lookupStarted(LookupEvent e) { 
　　System.out.println("Lookup started: " + e.getSearchText());
　}
　public void lookupCompleted(LookupEvent e) {
　　System.out.println("Lookup completed: " + e.getSearchText() + " " + e.getResults()); 
　}
} 

　　现在，我们添加Logger作为在FixedFrame构造方法中的LookupManager的一个监听器。

public FixedFrame() {
　lookupManager = new LookupManager();
　lookupManager.addListener(this); 
　lookupManager.addListener(new Logger());
　initComponents();
　layoutComponents();
}  

　　现在你已经看到了添加新的事件、创建新的监听器－－向您展示了事件驱动方案的灵活性和扩展性。你会发现随着你更多地开发事件集中的程序，你会更加娴熟地在你的应用中创建通用动作。像其它所有事情一样，这只需要时间和经验。看起来在事件模型上已经做了很多研究，但是你还是需要把它和其它替代方案相比较。考虑开发时间成本；最重要的，这是一次性成本。一旦你创建好了监听器模型和它们的动作，以后向你的应用中添加监听器将是小菜一蝶。
</pre><br /><br /><br /><pre>线程

　　到现在，我们已经解决了上面的异步问题；通过监听器使组件脱耦，通过事件对象传递变量，通过事件产生和监听器的注册的组合决定执行的顺序。让我们回到线程问题，因为正是它把我们带到了这儿。实际上非常容易：因为我们已经有了异步功能的监听器，我们可以简单地让监听器自己决定它们应该在哪个线程中执行。考虑UI类和LookupManager的分离。UI类基于事件，决定需要什么处理。并且，该类也是Swing，而日志类不是。所以让UI类负责决定它应该在什么线程中执行将更加有意义。所以，让我们再次看看UI类。下面是没有线程的lookupCompleted()方法：

public void lookupCompleted(final LookupEvent e) {
　outputTA.setText(""); 
　String[] results = e.getResults(); 
　for (int i = 0; i &lt; results.length; i++) {
　　String result = results[i]; 
　　outputTA.setText(outputTA.getText() + "\n" + result); 
　}
} 

　　我们知道这将在非Swing线程中调用，因为该事件是直接在LookupManager中触发的，这将不是在Swing线程中执行。因为所有的代码功能上都是异步的（我们不必等待监听器方法允许结束后才调用其它代码），我们可以通过SwingUtilities.invokeLater()将这些代码改道到Swing线程。下面是新的方法，传入一个匿名Runnable到SwingUtilities.invokeLater():

public void lookupCompleted(final LookupEvent e) { 
　//notice the threading 
　SwingUtilities.invokeLater( new Runnable() { 
　　public void run() { 
　　　outputTA.setText("");
　　　String[] results = e.getResults(); 
　　　for (int i = 0; i &lt; results.length; i++) {
　　　　String result = results[i]; 
　　　　outputTA.setText(outputTA.getText() + "\n" + result);
　　　}
　　}
　}
);
} 

　　如果任何LookupListener不是在Swing线程中执行，我们可以在调用线程中执行监听器代码。作为一个原则，我们希望所有的监听器都迅速地接到通知。所以，如果你有一个监听器需要很多时间来处理自己的功能，你应该创建一个新的线程或者把耗时代码放入ThreadPool中等待执行。

　　最后的步骤是让LookupManager在非Swing线程中执行lookup。当前，LookupManager是在JButton的ActionListener的Swing线程中被调用的。现在是我们做出决定的时候，或者我们在JButton的ActionListener中引入一个新的线程，或者我们可以保证lookup自己在非Swing线程中执行，自己开始一个新的线程。我选择尽可能和Swing类贴近地管理Swing线程。这有助于把所有Swing逻辑封装在一起。如果我们把Swing线程逻辑添加到LookupManager，我们将引入了一层不必要的依赖。并且，对于LookupManager在非Swing线程环境中孵化自己的线程是完全没有必要的，比如一个非绘图的用户界面，在我们的例子中，就是Logger。产生不必要的新线程将损害到你应用的性能，而不是提高性能。LookupManager执行的很好，不管Swing线程与否－－所以，我喜欢把代码集中在那儿。

　　现在我们需要将JButton的ActionListener执行lookup的代码放在一个非Swing线程中。我们创建一个匿名的Thread，使用一个匿名的Runnable执行这个lookup。

private void searchButton_actionPerformed() {
　new Thread(){ 
　　public void run() {
　　　lookupManager.lookup(searchTF.getText());
　　}
　}.start();
} 

　　这就完成了我们的Swing线程。简单地在actionPerformed()方法中添加线程，确保监听器在新的线程中执行照顾到了整个线程问题。注意，我们不用处理像第一个例子那样的任何问题。通过把时间花费在定义一个事件驱动的体系，我们在和Swing线程相关处理上节约了更多的时间。

　　结论

　　如果你需要在同一个方法中执行大量的Swing代码和非Swing代码，很容易将某些代码放错位置。事件驱动的方式将迫使你将代码放在它应该在的地方－－它仅应该在的地方。如果你在同一个方法中执行数据库调用和更新UI组件，那么你就在一个类中写入了太多的逻辑。分析你系统中的事件，创建底层的事件模型将迫使你将代码放到正确的地方。将费时的数据库调用代码放在非UI类中，也不要在非UI组件中更新UI组件。采用事件驱动的体系，UI负责UI更新，数据库管理类负责数据库调用。在这一点上，每一个封装的类都只用关心自己的线程，不用担心系统其它部分如何动作。当然，设计、构建一个事件驱动的客户端也很有用，但是需要花费的时间代价远超过带来的结果系统的灵活性和可维护性的提高。

</pre><br /><br /><br /></font>
<img src ="http://www.blogjava.net/xixidabao/aggbug/47235.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-05-20 23:59 <a href="http://www.blogjava.net/xixidabao/archive/2006/05/20/47235.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java线程总结 </title><link>http://www.blogjava.net/xixidabao/archive/2006/05/08/44934.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Sun, 07 May 2006 16:42:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/05/08/44934.html</guid><description><![CDATA[
		<font size="+0">版權申明,獲得授權轉載必須保留以下申明和鏈接:<br />作者的blog:(<a href="http://blog.matrix.org.cn/page/Kaizen" target="_new"><u><font color="#0000ff">http://blog.matrix.org.cn/page/Kaizen</font></u></a>)<br /><br />在论坛上面常常看到初学者对线程的无可奈何，所以总结出了下面一篇文章，希望对一些正在学习使用java线程的初学者有所帮助。<br /><br />首先要理解线程首先需要了解一些基本的东西，我们现在所使用的大多数操作系统都属于多任务，分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。什么是分时操作系统呢，通俗一点与就是可以同一时间执行多个程序的操作系统，在自己的电脑上面，你是不是一边听歌，一边聊天还一边看网页呢？但实际上，并不上cpu在同时执行这些程序，cpu只是将时间切割为时间片，然后将时间片分配给这些程序，获得时间片的程序开始执行，不等执行完毕，下个程序又获得时间片开始执行，这样多个程序轮流执行一段时间，由于现在cpu的高速计算能力，给人的感觉就像是多个程序在同时执行一样。<br />一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的，所以线程出现了。在java中，程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.（你可以将前面一句话的程序换成进程，进程是程序的一次执行过程,是系统运行程序的基本单位）<br /><br />线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.<br /><br />多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程，同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.<br /><br />[线程的运行机制以及调度模型] <br />java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力，每个线程可以独立于其他线程而独立运行，当然也可以和其他线程协同运行，一个类控制着它的所有线程，可以决定哪个线程得到优先级，哪个线程可以访问其他类的资源，哪个线程开始执行，哪个保持休眠状态。<br />下面是线程的机制图：<br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200604/20060404144316962.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><br />线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一：<br /><b>1.创建状态</b><br />使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)<br /><b>2.可运行状态</b><br />使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)<br /><b>3.运行中状态</b><br />Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.<br /><b>4.阻塞状态</b><br />一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)<br /><b>5.死亡状态</b><br />线程结束后是死亡状态(Dead)<br /><br />同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.<br />线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.<br /><br />线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.<br />独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占<br />分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行<br />分时方式的系统使每个线程工作若干步,实现多线程同时运行<br /><br />另外请注意下面的线程调度规则（如果有不理解，不急，往下看）：<br />①如果两个或是两个以上的线程都修改一个对象，那么把执行修改的方法定义为被同步的（Synchronized）,如果对象更新影响到只读方法，那么只度方法也应该定义为同步的<br />②如果一个线程必须等待一个对象状态发生变化，那么它应该在对象内部等待，而不是在外部等待，它可以调用一个被同步的方法，并让这个方法调用wait()<br />③每当一个方法改变某个对象的状态的时候，它应该调用notifyAll()方法，这给等待队列的线程提供机会来看一看执行环境是否已发生改变<br />④记住wait(),notify(),notifyAll()方法属于Object类，而不是Thread类，仔细检查看是否每次执行wait()方法都有相应的notify()或notifyAll()方法，且它们作用与相同的对象 在java中每个类都有一个主线程，要执行一个程序，那么这个类当中一定要有main方法，这个man方法也就是java class中的主线程。你可以自己创建线程，有两种方法，一是继承Thread类，或是实现Runnable接口。一般情况下，最好避免继承，因为java中是单根继承，如果你选用继承，那么你的类就失去了弹性，当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继承情况。至于选用那一种，具体情况具体分析。<br /><br /><br /><b>eg.继承Thread</b><br /><pre class="overflow">public class MyThread_1 extends Thread<br />{<br />public void run()<br />{<br />//some code <br />}<br />}</pre><br /><br /><b>eg.实现Runnable接口</b><br /><pre class="overflow">public class MyThread_2 implements Runnable<br />{<br />public void run()<br />{<br />//some code <br />}<br />}</pre><br /><br /><br />当使用继承创建线程，这样启动线程：<br /><pre class="overflow">new MyThread_1().start()</pre><br /><br />当使用实现接口创建线程，这样启动线程：<br /><pre class="overflow">new Thread(new MyThread_2()).start()</pre><br /><br />注意，其实是创建一个线程实例，并以实现了Runnable接口的类为参数传入这个实例，当执行这个线程的时候，MyThread_2中run里面的代码将被执行。<br />下面是完成的例子：<br /><br /><pre class="overflow">public class MyThread implements Runnable<br />{ <br /><br />public void run()<br />{ <br />System.out.println("My Name is "+Thread.currentThread().getName()); <br />} <br />public static void main(String[] args)<br />{<br />new Thread(new MyThread()).start(); <br />}<br />} </pre><br /><br /><br />执行后将打印出：<br /><span style="COLOR: blue">My Name is Thread-0</span><br /><br />你也可以创建多个线程，像下面这样<br /><pre class="overflow">new Thread(new MyThread()).start();<br />new Thread(new MyThread()).start();<br />new Thread(new MyThread()).start();</pre><br /><br /><br />那么会打印出：<br /><span style="COLOR: blue">My Name is Thread-0<br />My Name is Thread-1<br />My Name is Thread-2</span><br /><br />看了上面的结果，你可能会认为线程的执行顺序是依次执行的，但是那只是一般情况，千万不要用以为是线程的执行机制；影响线程执行顺序的因素有几点：首先看看前面提到的优先级别<br /><br /><br /><pre class="overflow">public class MyThread implements Runnable<br />{ <br /><br />public void run()<br />{ <br />System.out.println("My Name is "+Thread.currentThread().getName()); <br />} <br />public static void main(String[] args)<br />{<br />Thread t1=new Thread(new MyThread());<br />Thread t2=new Thread(new MyThread());<br />Thread t3=new Thread(new MyThread());<br />t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级<br />t1.start();<br />t2.start();<br />t3.start();<br />}<br />} </pre><br /><br />再看看结果：<br /><span style="COLOR: blue">My Name is Thread-1<br />My Name is Thread-0<br />My Name is Thread-2</span><br /><br /><br />线程的优先级分为10级，分别用1到10的整数代表，默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10）<br />然后是线程程序本身的设计，比如使用sleep,yield,join，wait等方法（详情请看JDKDocument)<br /><br /><pre class="overflow">public class MyThread implements Runnable<br />{ <br />public void run()<br />{ <br />try<br />{<br />int sleepTime=(int)(Math.random()*100);//产生随机数字，<br />Thread.currentThread().sleep(sleepTime);//让其休眠一定时间，时间又上面sleepTime决定<br />//public static void sleep(long millis)throw InterruptedException （API）<br />System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);<br />}catch(InterruptedException ie)//由于线程在休眠可能被中断，所以调用sleep方法的时候需要捕捉异常<br />{<br />ie.printStackTrace();<br />} <br />} <br />public static void main(String[] args)<br />{<br />Thread t1=new Thread(new MyThread());<br />Thread t2=new Thread(new MyThread());<br />Thread t3=new Thread(new MyThread());<br />t1.start();<br />t2.start();<br />t3.start();<br />}<br />}</pre><br /><br />执行后观察其输出：<br /><br /><span style="COLOR: blue">Thread-0 睡了 11<br />Thread-2 睡了 48<br />Thread-1 睡了 69</span><br /><br /><br /><br />上面的执行结果是随机的，再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句，当线程休眠的时候就会让出cpu，cpu将会选择执行处于runnable状态中的其他线程，当然也可能出现这种情况，休眠的Thread立即进入了runnable状态，cpu再次执行它。<br />[线程组概念]<br />线程是可以被组织的，java中存在线程组的概念，每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java的线程组由java.lang包中的Thread——Group类实现.<br />ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。<br />借用上面的例子，main里面可以这样写：<br /><br /><pre class="overflow">public static void main(String[] args)<br />{<br />/***************************************<br />ThreadGroup(String name) <br />ThreadGroup(ThreadGroup parent, String name) <br />***********************************/<br />ThreadGroup group1=new ThreadGroup("group1");<br />ThreadGroup group2=new ThreadGroup(group1,"group2");<br />Thread t1=new Thread(group2,new MyThread());<br />Thread t2=new Thread(group2,new MyThread());<br />Thread t3=new Thread(group2,new MyThread());<br />t1.start();<br />t2.start();<br />t3.start();<br />}</pre><br /><br /><br />线程组的嵌套，t1,t2,t3被加入group2,group2加入group1。<br />另外一个比较多就是关于线程同步方面的，试想这样一种情况，你有一笔存款在银行，你在一家银行为你的账户存款，而你的妻子在另一家银行从这个账户提款，现在你有1000块在你的账户里面。你存入了1000，但是由于另一方也在对这笔存款进行操作，人家开始执行的时候只看到账户里面原来的1000元，当你的妻子提款1000元后，你妻子所在的银行就认为你的账户里面没有钱了，而你所在的银行却认为你还有2000元。<br />看看下面的例子：<br /><br /><pre class="overflow">class BlankSaving //储蓄账户<br />{<br />private static int money=10000;<br />public void add(int i)<br />{<br />money=money+i;<br />System.out.println("Husband 向银行存入了 [￥"+i+"]");<br />}<br />public void get(int i)<br />{<br />money=money-i;<br />System.out.println("Wife 向银行取走了 [￥"+i+"]");<br />if(money&lt;0)<br />System.out.println("余额不足！"); <br />}<br />public int showMoney()<br />{<br />return money;<br />}<br />} <br /><br /><br />class Operater implements Runnable<br />{<br />String name;<br />BlankSaving bs;<br />public Operater(BlankSaving b,String s)<br />{<br />name=s;<br />bs=b;<br /><br /><br /><br />}<br />public static void oper(String name,BlankSaving bs)<br />{<br /><br /><br /><br />if(name.equals("husband"))<br />{<br />try<br />{<br />for(int i=0;i&lt;10;i++)<br />{<br />Thread.currentThread().sleep((int)(Math.random()*300));<br />bs.add(1000);<br />}<br />}catch(InterruptedException e){}<br />}else<br />{<br />try<br />{<br /><br /><br /><br />for(int i=0;i&lt;10;i++)<br />{<br />Thread.currentThread().sleep((int)(Math.random()*300));<br />bs.get(1000);<br />}<br />}catch(InterruptedException e){}<br />}<br />}<br />public void run()<br />{<br />oper(name,bs);<br />} <br />}<br />public class BankTest <br />{<br />public static void main(String[] args)throws InterruptedException<br />{<br />BlankSaving bs=new BlankSaving();<br />Operater o1=new Operater(bs,"husband");<br />Operater o2=new Operater(bs,"wife");<br />Thread t1=new Thread(o1);<br />Thread t2=new Thread(o2);<br />t1.start();<br />t2.start();<br />Thread.currentThread().sleep(500);<br />}<br /><br /><br /><br />}</pre><br /><br /><br />下面是其中一次的执行结果：<br /><br /><br /><br /><span style="COLOR: blue">---------first--------------<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Husband 向银行存入了 [￥1000]</span><br /><br />看到了吗，这可不是正确的需求，在husband还没有结束操作的时候，wife就插了进来，这样很可能导致意外的结果。解决办法很简单，就是将对数据进行操作方法声明为synchronized,当方法被该关键字声明后，也就意味着，如果这个数据被加锁，只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也就是当你存款的时候，这笔账户在其他地方是不能进行操作的，只有你存款完毕，银行管理人员将账户解锁，其他人才能对这个账户进行操作。<br />修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs)，再看看结果:<br /><br /><span style="COLOR: blue">Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Husband 向银行存入了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]<br />Wife 向银行取走了 [￥1000]</span><br /><br /><br /><br />当丈夫完成操作后，妻子才开始执行操作，这样的话，对共享对象的操作就不会有问题了。<br />[wait and notify]<br />你可以利用这两个方法很好的控制线程的执行流程，当线程调用wait方法后，线程将被挂起，直到被另一线程唤醒（notify）或则是如果wait方法指定有时间得话，在没有被唤醒的情况下，指定时间时间过后也将自动被唤醒。但是要注意一定，被唤醒并不是指马上执行，而是从组塞状态变为可运行状态，其是否运行还要看cpu的调度。<br />事例代码：<br /><br /><pre class="overflow">class MyThread_1 extends Thread<br />{<br />Object lock;<br />public MyThread_1(Object o)<br />{<br />lock=o;<br />}<br />public void run()<br />{<br />try<br />{<br />synchronized(lock)<br />{<br />System.out.println("Enter Thread_1 and wait");<br />lock.wait();<br />System.out.println("be notified");<br />}<br />}catch(InterruptedException e){}<br />}<br />}<br />class MyThread_2 extends Thread<br />{<br />Object lock;<br />public MyThread_2(Object o)<br />{<br />lock=o;<br />}<br />public void run()<br />{<br />synchronized(lock)<br />{<br />System.out.println("Enter Thread_2 and notify");<br />lock.notify();<br />}<br />}<br />}<br />public class MyThread<br />{ <br />public static void main(String[] args)<br />{<br />int[] in=new int[0];//notice<br />MyThread_1 t1=new MyThread_1(in);<br />MyThread_2 t2=new MyThread_2(in);<br />t1.start();<br />t2.start();<br />}<br />}</pre><br /><br /><br /><br />执行结果如下：<br /><span style="COLOR: blue">Enter Thread_1 and wait<br />Enter Thread_2 and notify<br />Thread_1 be notified</span><br /><br />可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法，这是由于调用这两个方法的时候线程必须获得锁，也就是上面代码中的lock[]，如果你不用synchronized包装这两个方法的得话，又或则锁不一是同一把，比如在MyThread_2中synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notify方法是Object中的，并不在Thread这个类中。最后你可能注意到了这点：int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组，那是因为在java中创建一个0长度的数组来充当锁更加高效。<br /><br />Thread作为java中一重要组成部分，当然还有很多地方需要更深刻的认识，上面只是对Thread的一些常识和易错问题做了一个简要的总结，若要真正的掌握java的线程，还需要自己多做总结 
<div></div><br /><br /><br /></font>
<img src ="http://www.blogjava.net/xixidabao/aggbug/44934.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-05-08 00:42 <a href="http://www.blogjava.net/xixidabao/archive/2006/05/08/44934.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>