??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲欧洲国产精品香蕉网,亚洲国产精品第一区二区,亚洲精品无码成人片在线观看http://www.tkk7.com/xixidabao/category/15377.htmlGROW WITH JAVAzh-cnThu, 23 Aug 2007 12:19:30 GMTThu, 23 Aug 2007 12:19:30 GMT60JAVA教程Q解析Java的多U程机制http://www.tkk7.com/xixidabao/archive/2006/09/12/69221.htmlJAVA之\JAVA之\Tue, 12 Sep 2006 11:50:00 GMThttp://www.tkk7.com/xixidabao/archive/2006/09/12/69221.html  
  q程QProcessQ是最初定义在Unix{多用户、多d操作pȝ环境下用于表C应用程序在内存环境中基本执行单元的概念。以Unix操作pȝZQ进E是Unix操作pȝ环境中的基本成分、是pȝ资源分配的基本单位。Unix操作pȝ中完成的几乎所有用L理和资源分配{工作都是通过操作pȝ对应用程序进E的控制来实现的?
  
  C?a target="_blank">C++?a target="_blank">Java{语a~写的源E序l相应的~译器编译成可执行文件后Q提交给计算机处理器q行。这Ӟ处在可执行状态中的应用程序称E。从用户角度来看Q进E是应用E序的一个执行过E。从操作pȝ核心角度来看Q进E代表的是操作系l分配的内存、CPU旉片等资源的基本单位,是ؓ正在q行的程序提供的q行环境。进E与应用E序的区别在于应用程序作Z个静态文件存储在计算机系l的盘{存储空间中Q而进E则是处于动态条件下由操作系l维护的pȝ资源理实体。多d环境下应用程序进E的主要特点包括Q?
  
  ●进E在执行q程中有内存单元的初始入口点Qƈ且进E存z过E中始终拥有独立的内存地址I间Q?
  
  ●进E的生存期状态包括创建、就l、运行、阻塞和M{类型;
  
  ●从应用E序q程在执行过E中向CPU发出的运行指令Ş式不同,可以进E的状态分为用h和核心态。处于用h下的进E执行的是应用程序指令、处于核心态下的应用程序进E执行的是操作系l指令?
  
  在Unix操作pȝ启动q程中,pȝ自动创徏swapper、init{系l进E,用于理内存资源以及对用戯E进行调度等。在Unix环境下无论是由操作系l创建的q程q要由应用程序执行创建的q程Q均拥有唯一的进E标识(PIDQ?

二、进E与JavaU程的区?
  
  
  应用E序在执行过E中存在一个内存空间的初始入口点地址、一个程序执行过E中的代码执行序列以及用于标识进E结束的内存出口点地址Q在q程执行q程中的每一旉点均有唯一的处理器指o与内存单元地址相对应?
  
  Java语言中定义的U程QThreadQ同样包括一个内存入口点地址、一个出口点地址以及能够序执行的代码序列。但是进E与U程的重要区别在于线E不能够单独执行Q它必须q行在处于活动状态的应用E序q程中,因此可以定义U程是程序内部的hq发性的序代码?
  
  Unix操作pȝ和Microsoft Windows操作pȝ支持多用戗多q程的ƈ发执行,而Java语言支持应用E序q程内部的多个执行线E的q发执行。多U程的意义在于一个应用程序的多个逻辑单元可以q发地执行。但是多U程q不意味着多个用户q程在执行,操作pȝ也不把每个线E作为独立的q程来分配独立的pȝ资源。进E可以创建其子进E,子进E与父进E拥有不同的可执行代码和数据内存I间。而在用于代表应用E序的进E中多个U程׃n数据内存I间Q但保持每个U程拥有独立的执行堆栈和E序执行上下文(ContextQ?
  
  Z上述区别Q线E也可以UCؓdq程 (Light Weight ProcessQLWP)。不同线E间允许d协作和数据交换,使得在计机pȝ资源消耗等斚w非常廉h?
  
  U程需要操作系l的支持Q不是所有类型的计算机都支持多线E应用程序。JavaE序设计语言线E支持与语言q行环境l合在一P提供了多dq发执行的能力。这好比一个h在处理家务的q程中,衣服放到洗衣机中自动洗涤后大cx在电饭锅里,然后开始做菜。等菜做好了Q饭熟了同时衣服也洗好了?
  
  需要注意的是:在应用程序中使用多线E不会增?CPU 的数据处理能力。只有在多CPU 的计机或者在|络计算体系l构下,JavaE序划分为多个ƈ发执行线E后Q同时启动多个线E运行,使不同的U程q行在基于不同处理器的Java虚拟ZQ才能提高应用程序的执行效率?br />
另外Q如果应用程序必ȝ待网l连接或数据?/font>q接{数据吞吐速度相对较慢的资源时Q多U程应用E序是非常有利的。基于Internet的应用程序有必要是多U程cd的,例如Q当开发要支持大量客户机的服务?/font>端应用程序时Q可以将应用E序创徏成多U程形式来响应客L的连接请求,使每个连接用L占一个客Lq接U程。这P用户感觉服务器只接用戯己服务,从而羃短了服务器的客户端响应时间?
  
  
三、Java语言的多U程E序设计Ҏ
  
  
  利用Java语言实现多线E应用程序的Ҏ很简单。根据多U程应用E序l承或实现对象的不同可以采用两种方式Q一U是应用E序的ƈ发运行对象直接承Java的线E类ThreadQ另外一U方式是定义q发执行对象实现Runnable接口?
  
  l承Threadcȝ多线E程序设计方?
  
  Thread cLJDK中定义的用于控制U程对象的类Q在该类中封装了用于q行U程控制的方法。见下面的示例代码:
  
  [code]//Consumer.java
  import java.util.*;
  class Consumer extends Thread
  {
   int nTime;
   String strConsumer;
   public Consumer(int nTime, String strConsumer)
   {
   this.nTime = nTime;
   this.strConsumer = strConsumer;
   }
   public void run()
   {
  while(true)
  {
   try
  {
   System.out.println("Consumer name:"+strConsumer+"\n");
   Thread.sleep(nTime);
   }
  catch(Exception e)
  {
   e.printStackTrace();
   }
  }
   }
  static public void main(String args[])
  {
   Consumer aConsumer = new Consumer (1000, "aConsumer");
   aConsumer.start();
   Consumer bConsumer = new Consumer (2000, "bConsumer");
   bConsumer.start();
   Consumer cConsumer = new Consumer (3000, "cConsumer ");
   cConsumer.start();
  }
  } [/code]
  
  
  
  
  从上面的E序代码可以看出Q多U程执行CConsumerl承Java语言中的U程cThreadq且在mainҎ中创Z三个Consumer对象的实例。当调用对象实例的startҎӞ自动调用ConsumercM定义的runҎ启动对象U程q行。线E运行的l果是每间隔nTime旉打印出对象实例中的字W串成员变量strConsumer的内宏V?
  
  可以ȝ出承Threadcȝ多线E程序设计方法是使应用程序类l承Threadcdƈ且在该类的runҎ中实现ƈ发性处理过E?
  
  实现Runnable接口的多U程E序设计Ҏ
  
  Java语言中提供的另外一U实现多U程应用E序的方法是多线E对象实现Runnable接口q且在该cM定义用于启动U程的runҎ。这U定义方式的好处在于多线E应用对象可以承其它对象而不是必ȝ承Threadc,从而能够增加类定义的逻辑性?
  
  实现Runnable接口的多U程应用E序框架代码如下所C:
  
  //Consumer.java
  import java.util.*;
  class Consumer implements Runnable
  {
   ??
  public Consumer(int nTime, String strConsumer){?…}
  public void run(){?…}
  static public void main(String args[])
  {
  Thread aConsumer = new Thread(new Consumer(1000, "aConsumer"));
  aConsumer.start();
  //其它对象实例的运行线E?
   //??
   }
  }
  
  从上qC码可以看出:该类实现了Runnable接口q且在该cM定义了runҎ。这U多U程应用E序的实现方式与l承Threadcȝ多线E应用程序的重要区别在于启动多线E对象的Ҏ设计Ҏ不同。在上述代码中,通过创徏Thread对象实例q且应用对象作为创建Threadcd例的参数?

四、线E间的同?
  
  Java应用E序的多个线E共享同一q程的数据资源,多个用户U程在ƈ发运行过E中可能同时讉Kh敏感性的内容。在Java中定义了U程同步的概念,实现对共享资源的一致性维护。下面以W者最q开发的Ud通信计费pȝ中线E间同步控制ҎQ说明Java语言中多U程同步方式的实现过E?
  
  在没有多U程同步控制{略条g下的客户账户cd义框架代码如下所C:
  
  public class ReGISterAccount
  {
  float fBalance;
  //客户~费Ҏ
  public void deposit(float fFees){ fBalance += fFees; }
  //通话计费Ҏ
  public void withdraw(float fFees){ fBalance -= fFees; }
  ??
  }

  
  
  
  
  读者也怼认ؓQ上q程序代码完全能够满费系l实际的需要。确实,在单U程环境下该E序实是可靠的。但是,多进Eƈ发运行的情况是怎样的呢Q假讑֏生这U情况:客户在客h务中心进行缴费的同时正在利用Ud通信讑֤仅此通话Q客户通话l束时计费系l启动计费进E,而同时服务中心的工作人员也提交缴费进E运行。读者可以看到如果发生这U情况,对客戯̎L处理是不严肃的?
  
  如何解决q种问题呢?很简单,在RegisterAccountcL法定义中加上用于标识同步Ҏ的关键字synchronized。这P在同步方法执行过E中该方法涉及的׃n资源Q在上述代码中ؓfBalance成员变量Q将被加上共享锁Q以保在方法运行期间只有该Ҏ能够对共享资源进行访问,直到该方法的U程q行l束打开׃n锁,其它U程才能够访问这些共享资源。在׃n锁没有打开的时候其它访问共享资源的U程处于d状态?
  
  q行U程同步{略控制后的RegisterAccountcd义如下面代码所C:
  
  public class RegisterAccount
  {
  float fBalance;
  public synchronized void deposit(float fFees){ fBalance += fFees; }
  public synchronized void withdraw(float fFees){ fBalance -= fFees; }
  ??
  }

  
  从经q线E同步机制定义后的代码Ş式可以看出:在对׃n资源q行讉K的方法访问属性关键字QpublicQ后附加同步定义关键字synchronizedQ得同步方法在对共享资源访问的时候,些敏感资源附加共享锁来控制方法执行期间的资源独占性,实现了应用系l数据资源的一致性管理和l护?


五?JavaU程的管?
  
  
  U程的状态控?
  
  在这里需要明的是:无论采用l承Threadc还是实现Runnable接口来实现应用程序的多线E能力,都需要在该类中定义用于完成实际功能的runҎQ这个runҎUCؓU程体(Thread BodyQ。按照线E体在计机pȝ内存中的状态不同,可以线E分为创建、就l、运行、睡眠、挂起和M{类型。这些线E状态类型下U程的特征ؓQ?
  
  创徏状态:当利用new关键字创建线E对象实例后Q它仅仅作ؓ一个对象实例存在,JVM没有为其分配CPU旉片等U程q行资源Q?
  
  qA状态:在处于创建状态的U程中调用startҎ线E的状态{换ؓqA状态。这ӞU程已经得到除CPU旉之外的其它系l资源,只等JVM的线E调度器按照U程的优先对该U程q行调度Q从而该线E拥有能够获得CPU旉片的Z?
  
  睡眠状态:在线E运行过E中可以调用sleepҎq在Ҏ参数中指定线E的睡眠旉线E状态{换ؓ睡眠状态。这Ӟ该线E在不释攑֍用资源的情况下停止运行指定的睡眠旉。时间到辑֐Q线E重新由JVMU程调度器进行调度和理?
  
  挂v状态:可以通过调用suspendҎ线E的状态{换ؓ挂v状态。这ӞU程释攑֍用的所有资源,由JVM调度转入临时存储I间Q直臛_用程序调用resumeҎ恢复U程q行?
  
  M状态:当线E体q行l束或者调用线E对象的stopҎ后线E将l止q行Q由JVM收回U程占用的资源?
  
  在JavaU程cM分别定义了相应的ҎQ用于在应用E序中对U程状态进行控制和理?
  
  U程的调?
  
  U程调用的意义在于JVM应对q行的多个线E进行系l的协调,以避免多个线E争用有限资源而导致应用系l死机或者崩溃?
  
  ZU程对于操作pȝ和用L重要性区分开QJava定义了线E的优先U策略。Java线E的优先U分?0个等U,分别?-10之间的数字表C。数字越大表明线E的U别高。相应地Q在ThreadcM定义了表C线E最低、最高和普通优先的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITYQ代表的优先U等U分别ؓ1?0?。当一个线E对象被创徏Ӟ光认的U程优先U是5?
  
  Z控制U程的运行策略,Java定义了线E调度器来监控系l中处于qA状态的所有线E。线E调度器按照U程的优先军_那个U程投入处理器运行。在多个U程处于qA状态的条g下,h高优先的线E会在低优先U线E之前得到执行。线E调度器同样采用"抢占?{略来调度线E执行,卛_前线E执行过E中有较高优先的线E进入就l状态,则高优先U的U程立即被调度执行。具有相同优先的所有线E采用轮转的方式来共同分配CPU旉片?
  
  在应用程序中讄U程优先U的Ҏ很简单,在创建线E对象之后可以调用线E对象的setPriorityҎ改变该线E的q行优先U,同样可以调用getPriorityҎ获取当前U程的优先?
  
  在Java中比较特D的U程是被UCؓ守护QDaemonQ线E的低别线E。这个线E具有最低的优先U,用于为系l中的其它对象和U程提供服务。将一个用LE设|ؓ守护U程的方式是在线E对象创Z前调用线E对象的setDaemonҎ。典型的守护U程例子是JVM中的pȝ资源自动回收U程Q它始终在低U别的状态中q行Q用于实时监控和理pȝ中的可回收资源?
  
  U程分组理
  
  Java定义了在多线E运行系l中的线E组QThreadGroupQ对象,用于实现按照特定功能对线E进行集中式分组理。用户创建的每个U程均属于某U程l,q个U程l可以在U程创徏时指定,也可以不指定U程l以使该U程处于默认的线E组之中。但是,一旦线E加入某U程l,该线E就一直存在于该线E组中直至线E死亡,不能在中途改变线E所属的U程l?
  
  当Java的Application应用E序q行ӞJVM创徏名称为main的线E组。除非单独指定,在该应用E序中创建的U程均属于mainU程l。在mainU程l中可以创徏其它名称的线E组q将其它U程加入到该U程l中Q依此类推,构成U程和线E组之间的树型管理和l承关系?
  
  与线E类|可以针对U程l对象进行线E组的调度、状态管理以及优先讄{。在对线E组q行理q程中,加入到某U程l中的所有线E均被看作统一的对象?

六、小l:
本文针对Javaq_中线E的性质和应用程序的多线E策略进行了分析和讲解?
  
  与其它操作系l环境不同,Javaq行环境中的U程cM于多用户、多d操作pȝ环境下的q程Q但在进E和U程的运行及创徏方式{方面,q程与JavaU程h明显区别?
  
  Unix操作pȝ环境下,应用E序可以利用fork函数创徏子进E,但子q程与该应用E序q程拥有独立的地址I间、系l资源和代码执行单元Qƈ且进E的调度是由操作pȝ来完成的Q得在应用q程之间q行通信和线E协调相对复杂。而Java应用E序中的多线E则是共享同一应用pȝ资源的多个ƈ行代码执行体Q线E之间的通信和协调方法相对简单?
  
  可以_Java语言对应用程序多U程能力的支持增ZJava作ؓ|络E序设计语言的优势,为实现分布式应用pȝ中多客户端的q发讉K以及提高服务器的响应效率奠定坚实基础?br />

JAVA之\ 2006-09-12 19:50 发表评论
]]>
java多线E设计模?/title><link>http://www.tkk7.com/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.tkk7.com/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多线E设计模?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语言已经内置了多U程支持Q所有实现Runnable接口的类都可被启动一个新U程Q新U程会执行该实例的run()ҎQ当run()Ҏ执行完毕后,U程q束了。一旦一个线E执行完毕,q个实例׃能再重新启动Q只能重新生成一个新实例Q再启动一个新U程?</p> <p>ThreadcL实现了Runnable接口的一个实例,它代表一个线E的实例Qƈ且,启动U程的唯一Ҏ是通过Threadcȝstart()实例ҎQ?/p> <p>Thread t = new Thread();<br>t.start();</p> <p>start()Ҏ是一个nativeҎQ它启动一个新U程Qƈ执行run()Ҏ。Threadc默认的run()Ҏ什么也不做退Z。注意:直接调用run()Ҏq不会启动一个新U程Q它和调用一个普通的javaҎ没有什么区别?/p> <p>因此Q有两个Ҏ可以实现自己的线E:</p> <p>Ҏ1Q自qcextend ThreadQƈ复写run()ҎQ就可以启动新线Eƈ执行自己定义的run()Ҏ。例如:</p> <p>public class MyThread extends Thread {<br>public run() {<br>System.out.println("MyThread.run()");<br>}<br>}</p> <p>在合适的地方启动U程Qnew MyThread().start();</p> <p>Ҏ2Q如果自qcdlextends另一个类Q就无法直接extends ThreadQ此Ӟ必须实现一个Runnable接口Q?/p> <p>public class MyThread extends OtherClass implements Runnable {<br>public run() {<br>System.out.println("MyThread.run()");<br>}<br>}</p> <p>Z启动MyThreadQ需要首先实例化一个ThreadQƈ传入自己的MyThread实例Q?/p> <p>MyThread myt = new MyThread();<br>Thread t = new Thread(myt);<br>t.start();</p> <p>事实上,当传入一个Runnable target参数lThread后,Thread的run()Ҏ׃调用target.run()Q参考JDK源代码:</p> <p>public void run() {<br>if (target != null) {<br>target.run();<br>}<br>}</p> <p>U程q有一些Name, ThreadGroup, isDaemon{设|,׃和线E设计模式关联很,q里׃多说了?/p> <p>׃同一q程内的多个U程׃n内存I间Q在Java中,是׃n实例Q当多个U程试图同时修改某个实例的内ҎQ就会造成冲突Q因此,U程必须实现׃n互斥Q多线E同步?/p> <p>最单的同步是将一个方法标CؓsynchronizedQ对同一个实例来_M时刻只能有一个synchronizedҎ在执行。当一个方法正在执行某个synchronizedҎӞ其他U程如果惌执行q个实例的Q意一个synchronizedҎQ都必须{待当前执行 synchronizedҎ的线E退出此Ҏ后,才能依次执行?/p> <p>但是Q非synchronizedҎ不受影响Q不当前有没有执行synchronizedҎQ非synchronizedҎ都可以被多个U程同时执行?/p> <p>此外Q必L意,只有同一实例的synchronizedҎ同一旉只能被一个线E执行,不同实例的synchronizedҎ是可以ƈ发的。例如,class A定义了synchronizedҎsync()Q则不同实例a1.sync()和a2.sync()可以同时׃个线E来执行?/p> <p>多线E同步的实现最l依赖锁机制。我们可以想象某一׃n资源是一间屋子,每个人都是一个线E。当A希望q入戉KӞ他必获得门锁,一旦A获得门锁Q他q去后就立刻门锁上Q于是B,C,D...׃得不在门外等待,直到A释放锁出来后QB,C,D...中的某一人抢C该锁Q具体抢法依赖于 JVM的实玎ͼ可以先到先得Q也可以随机挑选)Q然后进屋又门锁上。这PM时刻最多有一人在屋内Q用共享资源)?/p> <p>Java语言规范内置了对多线E的支持。对于JavaE序来说Q每一个对象实例都有一?#8220;?#8221;Q一旦某个线E获得了该锁Q别的线E如果希望获得该锁,只能{待q个U程释放锁之后。获得锁的方法只有一个,是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; } 事实上相当于Q?/p> <p>public void setCount(int count) {<br>synchronized(this) { // 在此获得this?br>this.count = count;<br>} // 在此释放this?br>}</p> <p>U色部分表示需要同步的代码D,该区域ؓ“危险区域”Q如果两个以上的U程同时执行Q会引发冲突Q因此,要更改SharedResource的内部状态,必须先获得SharedResource实例的锁?/p> <p>退出synchronized块时Q线E拥有的锁自动释放,于是Q别的线E又可以获取该锁了?/p> <p>Z提高性能Q不一定要锁定thisQ例如,SharedResource有两个独立变化的变量Q?/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()QsetB()时无法setA()。ؓ了提高性能Q可以用不同对象的锁:</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>通常Q多U程之间需要协调工作。例如,览器的一个显C图片的U程displayThread惌执行昄囄的Q务,必须{待下蝲U程 downloadThread该囄下蝲完毕。如果图片还没有下蝲完,displayThread可以暂停Q当downloadThread完成了Q务后Q再通知displayThread“囄准备完毕Q可以显CZ”Q这ӞdisplayThreadl箋执行?/p> <p>以上逻辑单的说就是:如果条g不满I则等待。当条g满Ӟ{待该条件的U程被唤醒。在Java中,q个机制的实C赖于wait/notify。等待机制与锁机制是密切兌的。例如:</p> <p>synchronized(obj) {<br>while(!condition) {<br>obj.wait();<br>}<br>obj.doSomething();<br>}</p> <p>当线EA获得了obj锁后Q发现条件condition不满I无法l箋下一处理Q于是线EAwait()?/p> <p>在另一U程B中,如果B更改了某些条Ӟ使得U程A的condition条g满了,可以唤醒线EAQ?/p> <p>synchronized(obj) {<br>condition = true;<br>obj.notify();<br>}</p> <p>需要注意的概念是:</p> <p># 调用obj的wait(), notify()Ҏ前,必须获得obj锁,也就是必d在synchronized(obj) {...} 代码D内?/p> <p># 调用obj.wait()后,U程A释放了obj的锁Q否则线EB无法获得obj锁,也就无法在synchronized(obj) {...} 代码D内唤醒A?/p> <p># 当obj.wait()Ҏq回后,U程A需要再ơ获得obj锁,才能l箋执行?/p> <p># 如果A1,A2,A3都在obj.wait()Q则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM军_Q?/p> <p># obj.notifyAll()则能全部唤醒A1,A2,A3Q但是要l箋执行obj.wait()的下一条语句,必须获得obj锁,因此QA1,A2,A3只有一个有Z获得锁l执行,例如A1Q其余的需要等待A1释放obj锁之后才能l执行?/p> <p># 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此QA1,A2,A3虽被唤醒Q但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后QA1,A2,A3中的一个才有机会获得锁l箋执行?/p> <p>前面讲了wait/notify机制QThreadq有一个sleep()静态方法,它也能ɾU程暂停一D|间。sleep与wait的不同点是: sleepq不释放锁,q且sleep的暂停和wait暂停是不一L。obj.wait会ɾU程q入obj对象的等待集合中q等待唤醒?/p> <p>但是wait()和sleep()都可以通过interrupt()Ҏ打断U程的暂停状态,从而ɾU程立刻抛出InterruptedException?/p> <p>如果U程A希望立即l束U程BQ则可以对线EB对应的Thread实例调用interruptҎ。如果此ȝEB正在 wait/sleep/joinQ则U程B会立L出InterruptedExceptionQ在catch() {} 中直接return卛_安全地结束线E?/p> <p>需要注意的是,InterruptedException是线E自׃内部抛出的,q不是interrupt()Ҏ抛出的。对某一U程调用 interrupt()Ӟ如果该线E正在执行普通的代码Q那么该U程Ҏ׃会抛出InterruptedException。但是,一旦该U程q入?wait()/sleep()/join()后,׃立刻抛出InterruptedException?/p> <p>GuardedSuspention模式主要思想是:</p> <p>当条件不满ӞU程{待Q直到条件满xQ等待该条g的线E被唤醒?/p> <p>我们设计一个客LU程和一个服务器U程Q客LU程不断发送请求给服务器线E,服务器线E不断处理请求。当h队列为空Ӟ服务器线E就必须{待Q直到客L发送了h?/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>蓝色部分是服务器线E的{待条gQ而客LU程在放入了一个request后,׃服务器线E等待条件满I于是唤醒服务器线E?/p> <p>客户端线E: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<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>服务器线E: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>服务器线E在U色部分可能会阻塞,也就是说QQueue.getRequest是一个阻塞方法。这和java标准库的许多IOҎcM?/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<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>我们启动?个客LU程和一个服务器U程Q运行结果如下:</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处理来自不同客户端的h?/p> <p>思?/p> <p>Q: 服务器线E的wait条gwhile(queue.size()==0)能否换成if(queue.size()==0)?</p> <p>A: 在这个例子中可以Q因为服务器U程只有一个。但是,如果服务器线E有多个Q例如Web应用E序有多个线E处理ƈ发请求,q非常普遍)Q就会造成严重问题?/p> <p>Q: 能否用sleep(1000)代替wait()?</p> <p>A: l对不可以。sleep()不会释放锁,因此sleep期间别的U程Ҏ没有办法调用getRequest()和putRequest()Q导致所有相关线E都被阻塞?/p> <p>Q: (Request)queue.remove(0)可以攑ֈsynchronized() {}块外面吗Q?/p> <p>A: 不可以。因为while()是测试queueQremove()是用queueQ两者是一个原子操作,不能攑֜synchronized外面?/p> <p>ȝ</p> <p>多线E设计看似简单,实际上必非总l地考虑各种锁定/同步的条ӞE不心Q就可能出错。ƈ且,当线E较时Q很可能发现不了问题Q一旦问题出现又难以调试?/p> <p>所q的是,已有一些被验证q的模式可以供我们用,我们会l介l一些常用的多线E设计模式?/p> <p><br>前面谈了多线E应用程序能极大地改善用L应。例如对于一个Web应用E序Q每当一个用戯求服务器q接Ӟ服务器就可以启动一个新U程为用h务?/p> <p>然而,创徏和销毁线E本w就有一定的开销Q如果频J创建和销毁线E,CPU和内存开销׃可忽略,垃圾攉器还必须负担更多的工作。因此,U程池就是ؓ了避免频J创建和销毁线E?/p> <p>每当服务器接受了一个新的请求后Q服务器׃U程池中挑选一个等待的U程q执行请求处理。处理完毕后Q线Eƈ不结束,而是转ؓd状态再ơ被攑օU程池中。这样就避免了频J创建和销毁线E?/p> <p>Worker Pattern实现了类似线E池的功能。首先定义Task接口Q?/p> <p>package com.crackj2ee.thread;<br>public interface Task {<br>void execute();<br>}</p> <p>U程负责执行execute()Ҏ。注意到d是由子类通过实现execute()Ҏ实现的,U程本nq不知道自己执行的Q务。它只负责运行一个耗时的execute()Ҏ?/p> <p>具体d由子cd玎ͼ我们定义了一个CalculateTask和一个TimerTaskQ?/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>以上d均简单的sleep若干U?/p> <p>TaskQueue实现了一个队列,客户端可以将h攑օ队列Q服务器U程可以从队列中取出dQ?/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>l于C真正的WorkerThreadQ这是真正执行Q务的服务器线E:</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>前面已经讲过Qqueue.getTask()是一个阻塞方法,服务器线E可能在此wait()一D|间。此外,WorkerThreadq有一个shutdownҎQ用于安全结束线E?/p> <p>最后是ThreadPoolQ负责管理所有的服务器线E,q可以动态增加和减少U程敎ͼ</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()>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()Ҏ是ؓ了方便调试,打印出所有线E的当前状态?/p> <p>最后,Main负责完成main()ҎQ?/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<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个TaskQ然后动态添加了一些服务线E,q定期打印线E状态,q行l果如下Q?/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>仔细观察Q一开始只有两个服务器U程Q因此线E状态都是忙Q后来线E数增多Q?个线E中的两个状态变成idleQ说明处于wait()状态?/p> <p>思考:本例的线E调度算法其实根本没有,因ؓq个应用是围lTaskQueue设计的,不是以Thread PoolZ心设计的。因此,Task调度取决于TaskQueue的getTask()ҎQ你可以改进q个ҎQ例如用优先队列,使优先高的d先被执行?/p> <p>如果所有的服务器线E都处于busy状态,则说明Q务繁忙,TaskQueue的队列越来越长,最l会D服务器内存耗尽。因此,可以限制 TaskQueue的等待Q务数Q超q最大长度就拒绝处理。许多Web服务器在用户hJ忙时就会拒l用PHTTP 503 SERVICE UNAVAILABLE</p> <p>多线E读写同一个对象的数据是很普遍的,通常Q要避免d冲突Q必M证Q何时候仅有一个线E在写入Q有U程正在d的时候,写入操作必ȝ待。简单说Q就是要避免“??#8221;冲突?#8220;??#8221;冲突。但是同时读是允许的Q因?#8220;??#8221;不冲H,而且很安全?/p> <p>要实C上的ReadWriteLockQ简单的使用synchronized׃行,我们必须自己设计一个ReadWriteLockc,在读之前Q必d获得“读锁”Q写之前Q必d获得“写锁”。D例说明:</p> <p>DataHandler对象保存了一个可d的char[]数组Q?/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<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<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()Ҏ是非U程安全的读写方法。ؓ了演C,加入了sleep()Qƈ讄ȝ速度大约是写?倍,q符合通常的情c?/p> <p>Z让多U程能安全读写,我们设计了一个ReadWriteLockQ?/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>0 || (preferWrite && waitingThreads>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>0 || writingThreads>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()用于获得读锁QreadUnlock()释放读锁QwriteLock()和writeUnlock()一栗由于锁用完必须释放Q因此,必须保证lock和unlock匚w。我们修改DataHandlerQ加入ReadWriteLockQ?/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<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<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()完全装了底层的ReadWriteLockQ因此,多线E可以安全地调用q两个方法:</p> <p>// ReadingThread不断d数据Q?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不断写入数据Q每ơ写入的都是10个相同的字符Q?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随机字符Q填入char[10]:<br>private void fill(char[] data) {<br>char c = (char)(Math.random()*26+'A');<br>for(int i=0; i<data.length; i++)<br>data[i] = c;<br>}<br>}</p> <p>最后Main负责启动q些U程Q?/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<ts.length; i++) {<br>ts[i].start();<br>}<br>}<br>}</p> <p>我们启动?个读U程?个写U程Q运行结果如下:</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>可以看到Q每ơ读/写都是完整的原子操作Q因为我们每ơ写入的都是10个相同字W。ƈ且,每次d的都是最q一ơ写入的内容?/p> <p>如果LReadWriteLockQ?/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<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<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>q行l果如下Q?/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的过E中Q?个线E读取的内容是不同的?/p> <p>思?/p> <p>java的synchronized提供了最底层的物理锁Q要在synchronized的基上,实现自己的逻辑锁,必Ml设计ReadWriteLock?/p> <p>Q: lock.readLock()Z么不攑օtry{ } 内?<br>A: 因ؓreadLock()会抛出InterruptedExceptionQ导致readingThreads++不执行,而readUnlock()?finally{ } 中,DreadingThreads--执行Q从而readingThread状态出错。writeLock()也是cM的?/p> <p>Q: preferWrite有用吗?<br>A: 如果LpreferWriteQ线E安全不受媄响。但是,如果dU程很多Q上一个线E还没有d完,下一个线E又开始读了,导致写入线E长旉无法获得writeLockQ如果写入线E等待的很多Q一个接一个写Q也会导致读取线E长旉无法获得readLock。preferWrite的作用是让读 /写交替执行,避免׃ȝE繁忙导致写无法q行和由于写U程J忙DL法进行?/p> <p>Q: notifyAll()换成notify()行不行?<br>A: 不可以。由于preferWrite的存在,如果一个线E刚d完毕Q此时preferWrite=trueQ再notify()Q若恰好唤醒的是一个读U程Q则while(writingThreads>0 || (preferWrite && waitingThreads>0))可能为trueD该读U程l箋{待Q而等待写入的U程也处于wait()中,l果所有线E都处于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>Q运行到此不动了Q?/p> <p>注意到这U死锁是׃所有线E都在等待别的线E唤醒自己,l果都无法醒q来。这和两个线E希望获得对方已有的锁造成死锁不同。因此多U程设计的难度远q高于单U程应用?/p> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <img src ="http://www.tkk7.com/xixidabao/aggbug/53641.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/xixidabao/" target="_blank">JAVA之\</a> 2006-06-18 21:38 <a href="http://www.tkk7.com/xixidabao/archive/2006/06/18/53641.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>再论Java SwingU程http://www.tkk7.com/xixidabao/archive/2006/05/20/47235.htmlJAVA之\JAVA之\Sat, 20 May 2006 15:59:00 GMThttp://www.tkk7.com/xixidabao/archive/2006/05/20/47235.html
  不正的SwingU程是运行缓慢、无响应和不E_的Swing应用的主要原因之一。这是许多原因造成的,从开发h员对Swing单线E模型的误解Q到保证正确的线E执行的困难。即使对SwingU程q行了很多努力,应用U程逻辑也是很难理解和维护的。本文阐qC如何在开发Swing应用中用事仉动编E,以大大简化开发、维护,q提供高灉|性?

  背景

  既然我们是要化Swing应用的线E,首先让我们来看看SwingU程是怎么工作的,Z么它是必ȝ。Swing API是围l单U程模型设计的。这意味着Swinglg必须L通过同一个线E来修改和操Ucؓ什么采用单U程模型Q这有很多原因,包括开发成本和同步Swing的复杂性-Q这都会造成一个迟钝的API。ؓ了达到单U程模型Q有一个专门的U程用于和Swinglg交互。这个线E就是大家熟知的SwingU程QAWTQ有时也发音为“ought”)U程Q或者事件分zE。在本文的下面的部分Q我选用SwingU程的叫法?
既然SwingU程是和Swinglgq行交互的唯一的线E,它就被赋予了很多责Q。所有的l制和图形,鼠标事gQ组件事Ӟ按钮事gQ和所有其它事仉发生在SwingU程。因为SwingU程的工作已l非常沉重了Q当太多其它工作在SwingU程中进行处理时׃发生问题。会引vq个问题的最常见的位|是在非Swing处理的地方,像发生在一个事件监听器Ҏ中,比如JButton的ActionListenerQ的数据库查找。既然ActionListener的actionPerformed()Ҏ自动在SwingU程中执行,那么Q数据库查找也将在SwingU程中执行。这占用了Swing的工作,L它处理它的其它Q务-Q像l制Q响应鼠标移动,处理按钮事gQ和应用的羃放。用户以为应用死掉了Q但实际上ƈ不是q样。在适当的线E中执行代码对确保系l正常地执行非常重要?

  既然我们已经看到了在适当的线E中执行Swing应用的代码是多么重要Q现在让我们如何实现q些U程。我们看看将代码攑օ和移出SwingU程的标准机制。在讲述q程中,我将H出几个和标准机制有关的问题和难炏V正如我们看到的Q大部分的问题都来自于企囑֜异步的SwingU程模型上实现同步的代码模型。从那儿Q我们将看到如何修改我们的例子到事g驱动Q-UL整个方式到异步模型?




通用SwingU程解决Ҏ

  让我们以一个最常用的SwingU程错误开始。我们将企图使用标准的技术来修正q个问题。在q个q程中,我们看到实现正的SwingU程的复杂性和常见困难。ƈ且,注意在修正这个SwingU程问题中,许多中间的例子也是不能工作的。在例子中,我在代码p|的地方以//broken开头标出。好了,现在Q让我们q入我们的例子吧?br />
  假设我们在执行图书查找。我们有一个简单的用户界面Q包括一个查找文本域Q一个查找按钮,和一个输出的文本区域。这个接口如?所C。不要批评我的UI设计Q这个确实很丑陋Q我承认?br />

?1. 基本查询用户界面

  用户输入书的标题Q作者或者其它条Ӟ然后昄一个结果的列表。下面的代码例子演示了按钮的ActionListener在同一个线E中调用lookup()Ҏ。在q些例子中,我用了thread.sleep()休眠5U来作ؓ一个占位的外部查找。线E休眠的l果{同于一个耗时5U的同步的服务器调用?br />
private void searchButton_actionPerformed()
{
 outputTA.setText("Searching for: " + searchTF.getText());
 //Broken!! Too much work in the Swing
 thread String[] results = lookup(searchTF.getText());
 outputTA.setText("");
 for (int i = 0; i < results.length; i++)
 {
  String result = results[i];
  outputTA.setText(outputTA.getText() + '\n' + result);
  }
}

  如果你运行这D代码(完整的代码可以在q儿下蝲Q,你会立即发现存在一些问题。图2昄了查找运行中的一个屏q截图?br />

?2. 在SwingU程中进行查?br />
  注意Go按钮看v来是被按下了。这是因为actionPerformedҎ通知了按钮绘制ؓ非按下外观,但是q没有返回。你也会发现要查扄字串“abcde”ƈ没有出现在文本区域中。searchButton_actionPerformed的第1行代码将文本区域讄查找的字丌Ӏ但是,注意Swing重画q不是立x行的。而是把重画请求放|到Swing事g队列中等待SwingU程处理。但是这儿,我们因查扑֤理占用了SwingU程Q所以,它还不能马上q行重画?br />
  要修正这些问题,让我们把查找操作Ud非SwingU程中。我们第一个想到的是让整个方法在一个新的线E中执行。这样作的问题是SwinglgQ本例中的文本区域,只能从SwingU程中进行编辑。下面是修改后的searchButton_actionPerformedҎQ?br />
private void searchButton_actionPerformed()
{
 outputTA.setText("Searching for: " + searchTF.getText());
 //the String[][] is used to allow access to
 // setting the results from an inner class
 final String[][] results = new String[1][1];
 new Thread()
 {
  public void run()
  {
   results[0] = lookup(searchTF.getText());
   }
  }.start();
 outputTA.setText("");
 for (int i = 0; i < results[0].length; i++)
  {
  String result = results[0][i];
  outputTA.setText(outputTA.getText() + '\n' + result);
  }
}

  q种Ҏ有很多问题。注意final String[][] 。这是一个处理匿名内部类和作用域的不得已的替代。基本上Q在匿名内部cM使用的,但在外部环绕cM用域中定义的M变量都需要定义ؓfinal。你可以通过创徏一个数l来持有变量解决q个问题。这L话,你可以创建数lؓfinal的,修改数组中的元素Q而不是数l的引用自n。既然我们已l解册个问题,让我们进入真正的问题所在吧。图3昄了这D代码运行时发生的情况:


?3. 在SwingU程外部q行查找

  界面昄了一个nullQ因为显CZ码在查找代码完成前被处理了。这是因Z旦新的线E启动了Q代码块l箋执行Q而不是等待线E执行完毕。这是那些奇怪的q发代码块中的一个,下面把它编写到一个方法中使其能够真正执行?br />
  在SwingUtilitiescM有两个方法可以帮助我们解册些问题:invokerLater()和invokeAndWait()。每一个方法都以一个Runnable作ؓ参数Qƈ在SwingU程中执行它。invokeAndWaitQ)Ҏd直到Runnnable执行完毕QinvokeLater()异步地执行Runnable。invokeAndWait()一般不赞成使用Q因为它可能D严重的线E死锁,对你的应用造成严重的破坏。所以,让我们把它放|一边,使用invokeLater()Ҏ?br />要修正最后一个变量变量scooping和执行顺序的问题Q我们必d文本区域的getText()和setText()Ҏ调用Ud一个RunnableQ只有在查询l果q回后再执行它,q且在SwingU程中执行。我们可以这样作Q创Z个匿名Runnable传递给invokeLater()Q包括在新线E的Runnable后的文本区域操作。这保证了Swing代码不会在查扄束之前执行。下面是修正后的代码Q?br />
private void searchButton_actionPerformed()
{
 outputTA.setText("Searching for: " + searchTF.getText());
 final String[][] results = new String[1][1];
 new Thread()
 {
  public void run()
  { //get results.
   results[0] = lookup(searchTF.getText())
   // send runnable to the Swing thread
   // the runnable is queued after the
   // results are returned
   SwingUtilities.invokeLater(
    new Runnable()
    {
     public void run()
     {
      // Now we're in the Swing thread
      outputTA.setText("");
      for (int i = 0; i < results[0].length; i++)
      {
       String result = results[0][i];
       outputTA.setText( outputTA.getText() + '\n' + result);
       }
      }
    }
   );
  }
 }.start();}

  q可以工作,但是q样做o人非常头痛。我们不得不寚w过匿名U程执行的顺序,我们q不得不处理困难的scooping问题。问题ƈ不少见,q且Q这只是一个非常简单的例子Q我们已l遇C作用域,变量传递,和执行顺序等一pd问题。相像一个更复杂的问题,包含了几层嵌套,׃n的引用和指定的执行顺序。这U方法很快就失控了?br />
问题

  我们在企囑ּ刉过异步模型q行同步执行Q-企图一个方形的螺栓攑ֈ一个圆形的IZ。只有我们尝试这样做Q我们就会不断地遭遇q些问题。从我的l验Q可以告诉你q些代码很难阅读Q很隄护,q且易于出错?

  q看h是一个常见的问题Q所以一定有标准的方式来解决Q对吗?出现了一些框架用于管理Swing的复杂性,所以让我们来快速预览一下它们可以做什么?

  一个可以得到的解决Ҏ是FoxtrotQ一个由Biorn Steedom写的框架Q可以在SourceForge上获取。它使用一个叫做Worker的对象来控制非Swingd在非SwingU程中的执行Q阻塞直到非Swingd执行完毕。它化了SwingU程Q允怽~写同步代码Qƈ在SwingU程和非SwingU程直接切换。下面是来自它的站点的一个例子:

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();} 

  注意它是如何解决上面的那些问题的。我们能够非常容易地在SwingU程中传入传出变量。ƈ且,代码块看h也很正确Q-先编写的先执行。但是仍然有一些问题障阻止用从准同步异步解x案。Foxtrot中的一个问题是异常理。用FoxtrotQ每ơ调用Worker必须捕获Exception。这是将执行代理lWorker来解军_步对异步问题的一个物?

  同样以非常相似的方式Q我此前也创Z一个框Ӟ我称它ؓ链接q行引擎QChained Runnable EngineQ?Q同样也遭受来自cM同步对异步问题的困扰。用这个框Ӟ你将创徏一个将被引擎执行的Runnable的集合。每一个Runnable都有一个指C器告诉引擎是否应该在SwingU程或者另外的U程中执行。引擎也保证Runnable以正的序执行。所以Runnable #2不会放入队列直到Runnable #1执行完毕。ƈ且,它支持变量以HashMap的Ş式从Runnable到Runnable传递?

  表面上,它看h解决了我们的主要问题。但是当你深入进dQ同L问题又冒出来了。本质上Q我们ƈ没有改变上面描述的Q何东西-Q我们只是将复杂性隐藏在引擎的后面。因为指数增长的Runnable而代码~写变得非常枯燥,也很复杂Qƈ且这些Runnable常常怺耦合。Runnable之间的非cd的HashMap变量传递变得难于管理。问题的列表q有很多?

  在编写这个框架之后,我意识到q需要一个完全不同的解决Ҏ。这让我重新审视了问题,看别人是怎么解决cM的问题的Qƈ深入的研I了Swing的源代码?




解决ҎQ事仉动编E?

  所有前面的q些解决Ҏ都存在一个共同的致命~陷Q-企图在持l地改变U程的同时表CZ个Q务的功能集。但是改变线E需要异步的模型Q而线E异步地处理Runnable。问题的部分原因是我们在企图在一个异步的U程模型之上实现一个同步的模型。这是所有Runnable之间的链和依赖,执行序和内部类scooping问题的根源。如果我们可以构建真正的异步Q我们就可以解决我们的问题ƈ极大地简化SwingU程?
在这之前Q让我们先列举一下我们要解决的问题:

  1. 在适当的线E中执行代码 

  2. 使用SwingUtilities.invokeLater()异步地执? 

  异步地执行导致了下面的问题:

  1. 互相耦合的组?

  2. 变量传递的困难 

  3. 执行的顺?

  让我们考虑一下像Java消息服务QJMSQ这LZ消息的系l,因ؓ它们提供了在异步环境中功能组件之间的松散耦合。消息系l触发异步事Ӟ正如在Enterprise Integration Patterns 中描q的。感兴趣的参与者监听该事gQƈ对事件做成响应-Q通常通过执行它们自己的一些代码。结果是一l模块化的,松散耦合的组Ӟlg可以d到或者从pȝ中去除而不影响到其它组件。更重要的,lg之间的依赖被最化了,而每一个组仉是良好定义的和封装的Q-每一个都仅对自己的工作负责。它们简单地触发消息Q其它一些组件将响应q个消息Qƈ对其它组件触发的消息q行响应?

  现在Q我们先忽略U程问题Q将lg解耦ƈUL到异步环境中。在我们解决了异步问题后Q我们将回过头来看看U程问题。正如我们所要看到的,那时解决q个问题非常容易?

  让我们还拿前面引入的例子Qƈ把它UL到基于事件的模型。首先,我们把lookup调用抽象C个叫LookupManager的类中。这允许我们将所有UIcM的数据库逻辑UdQƈ最l允许我们完全将q两者脱耦。下面是LookupManagercȝ代码Q?

class LookupManager { 
 private String[] lookup(String text) {
  String[] results = ... // database lookup code return results 
 }
} 

  现在我们开始向异步模型转换。ؓ了ɘq个调用异步化,我们需要抽象调用的q回。换句话Q方法不能返回Q何倹{我们将以分辨什么相关的动作是其它类所希望知道的开始。在我们q个例子中最明显的事件是搜烦l束事g。所以让我们创徏一个监听器接口来响应这些事件。该接口含有单个ҎlookupCompleted()。下面是接口的定义:

interface LookupListener { public void lookupCompleted(Iterator results);} 

  遵守Java的标准,我们创徏另外一个称作LookupEvent的类包含l果字串数组Q而不是到处直接传递字串数l。这允许我们在不改变LookupListener接口的情况下传递其它信息。例如,我们可以在LookupEvent中同时包括查扄字串和结果。下面是LookupEventc:

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; 
 }
} 

  注意LookupEventcL不可变的。这是很重要的,因ؓ我们q不知道在传递过E中谁将处理q些事g。除非我们创Z件的保护拯来传递给每一个监听者,我们需要把事g做成不可变的。如果不q样Q一个监听者可能会无意或者恶意地修订事g对象Qƈ破坏pȝ?

  现在我们需要在LookupManager上调用lookupComplete()事g。我们首先要在LookupManager上添加一个LookupListener的集合:

List listeners = new ArrayList(); 

  q提供在LookupManager上添加和去除LookupListener的方法:

public void addLookupListener(LookupListener listener){ 
 listeners.add(listener);
}
public void removeLookupListener(LookupListener listener){
 listeners.remove(listener);
} 

  当动作发生时Q我们需要调用监听者的代码。在我们的例子中Q我们将在查找返回时触发一个lookupCompleted()事g。这意味着在监听者集合上q代Qƈ使用一个LookupEvent事g对象调用它们的lookupCompleted()Ҏ?



我喜Ƣ把q些代码析取C个独立的Ҏfire[event-method-name] Q其中构造一个事件对象,在监听器集合上P代,q调用每一个监听器上的适当的方法。这有助于隔M要逻辑代码和调用监听器的代码。下面是我们的fireLookupCompletedҎQ?

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); 
 }
} 

  W?行代码创Z一个新的集合,传入原监听器集合。这在监听器响应事g后决定在LookupManager中去除自己时发挥作用。如果我们不是安全地拯集合Q在一些监听器应该 被调用而没有被调用时发生o人厌烦的错误?

  下面Q我们将在动作完成时调用fireLookupCompleted辅助Ҏ。这是lookupҎ的返回查询结果的l束处。所以我们可以改变lookupҎ使其触发一个事件而不是返回字串数l本w。下面是新的lookupҎQ?

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。我们希望当查找q回时更新文本区域。以前,我们只是直接调用setText()Ҏ。因为文本区域是和数据库调用一起都在UI中执行的。既然我们已l将查找逻辑从UI中抽象出来了Q我们将把UIcMZ个到LookupManager的监听器Q监听lookup事gq相应地更新自己。首先我们将在类定义中实现监听器接口Q?

public class FixedFrame implements LookupListener 

  接着我们实现接口ҎQ?

public void lookupCompleted(final LookupEvent e) {
 outputTA.setText("");
 String[] results = e.getResults(); 
 for (int i = 0; i < results.length; i++) {
  String result = results[i]; 
  outputTA.setText(outputTA.getText() + "\n" + result); 
  }
} 

  最后,我们它注册为LookupManager的一个监听器Q?

public FixedFrame() { 
 lookupManager = new LookupManager(); 
 //here we register the listener 
 lookupManager.addListener(this);
 initComponents(); 
 layoutComponents();} 

  Z化,我在cȝ构造器中将它添加ؓ监听器。这在大多数pȝ上都允许良好。当pȝ变得更加复杂Ӟ你可能会重构、从构造器中提炼出监听器注册代码,以允许更大的灉|性和扩展性?

  到现在ؓ止,你看C所有组件之间的q接Q注意职责的分离。用L面类负责信息的显C-Qƈ且仅负责信息的显C。另一斚wQLookupManagerc负责所有的lookupq接和逻辑。ƈ且,LookupManager负责在它变化旉知监听器-Q而不是当变化发生时应该具体做什么。这允许你连接Q意多的监听器?

  Z演示如何d新的事gQ让我们回头d一个lookup开始的事g。我们可以添加一个称作lookupStarted()的事件到LookupListenerQ我们将在查扑ּ始执行前触发它。我们也创徏一个fireLookupStarted()事g调用所有LookupListener的lookupStarted()。现在lookupҎ如下Q?

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()ҎQ除了我们调用监听器上的lookupStarted()ҎQƈ且该事g也不包含l果集。下面是代码Q?

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);
 }
} 

  最后,我们在UIcM实现lookupStarted()ҎQ设|文本区域提C当前搜索的字符丌Ӏ?

public void lookupStarted(final LookupEvent e) {
 outputTA.setText("Searching for: " + e.getSearchText());
} 

  q个例子展示了添加新的事件是多么Ҏ。现在,让我们看看展CZ仉动脱耦的灉|性。我们将通过创徏一个日志类Q当一个搜索开始和l束时在命o行中输出信息来演C。我们称q个cMؓLogger。下面是它的代码Q?

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()); 
 }
} 

  现在Q我们添加Logger作ؓ在FixedFrame构造方法中的LookupManager的一个监听器?

public FixedFrame() {
 lookupManager = new LookupManager();
 lookupManager.addListener(this); 
 lookupManager.addListener(new Logger());
 initComponents();
 layoutComponents();
}  

  现在你已l看Cd新的事g、创建新的监听器Q-向您展示了事仉动方案的灉|性和扩展性。你会发现随着你更多地开发事仉中的E序Q你会更加娴熟地在你的应用中创徏通用动作。像其它所有事情一Pq只需要时间和l验。看h在事件模型上已经做了很多研究Q但是你q是需要把它和其它替代Ҏ相比较。考虑开发时间成本;最重要的,q是一ơ性成本。一旦你创徏好了监听器模型和它们的动作,以后向你的应用中d监听器将是小菜一蝶?



U程

  到现在,我们已经解决了上面的异步问题Q通过监听器ɾlgp,通过事g对象传递变量,通过事g产生和监听器的注册的l合军_执行的顺序。让我们回到U程问题Q因为正是它把我们带Cq儿。实际上非常ҎQ因为我们已l有了异步功能的监听器,我们可以单地让监听器自己军_它们应该在哪个线E中执行。考虑UIcdLookupManager的分RUIcd于事Ӟ军_需要什么处理。ƈ且,该类也是SwingQ而日志类不是。所以让UIc负责决定它应该在什么线E中执行更加有意义。所以,让我们再ơ看看UIcR下面是没有U程的lookupCompleted()ҎQ?

public void lookupCompleted(final LookupEvent e) {
 outputTA.setText(""); 
 String[] results = e.getResults(); 
 for (int i = 0; i < results.length; i++) {
  String result = results[i]; 
  outputTA.setText(outputTA.getText() + "\n" + result); 
 }
} 

  我们知道q将在非SwingU程中调用,因ؓ该事件是直接在LookupManager中触发的Q这不是在SwingU程中执行。因为所有的代码功能上都是异步的Q我们不必等待监听器Ҏ允许l束后才调用其它代码Q,我们可以通过SwingUtilities.invokeLater()这些代码改道到SwingU程。下面是新的ҎQ传入一个匿名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 < results.length; i++) {
    String result = results[i]; 
    outputTA.setText(outputTA.getText() + "\n" + result);
   }
  }
 }
);
} 

  如果MLookupListener不是在SwingU程中执行,我们可以在调用线E中执行监听器代码。作Z个原则,我们希望所有的监听器都q速地接到通知。所以,如果你有一个监听器需要很多时间来处理自己的功能,你应该创Z个新的线E或者把耗时代码攑օThreadPool中等待执行?

  最后的步骤是让LookupManager在非SwingU程中执行lookup。当前,LookupManager是在JButton的ActionListener的SwingU程中被调用的。现在是我们做出军_的时候,或者我们在JButton的ActionListener中引入一个新的线E,或者我们可以保证lookup自己在非SwingU程中执行,自己开始一个新的线E。我选择可能和Swingc脓q地理SwingU程。这有助于把所有Swing逻辑装在一赗如果我们把SwingU程逻辑d到LookupManagerQ我们将引入了一层不必要的依赖。ƈ且,对于LookupManager在非SwingU程环境中孵化自qU程是完全没有必要的Q比如一个非l图的用L面,在我们的例子中,是Logger。生不必要的新U程损宛_你应用的性能Q而不是提高性能。LookupManager执行的很好,不管SwingU程与否Q-所以,我喜Ƣ把代码集中在那ѝ?

  现在我们需要将JButton的ActionListener执行lookup的代码放在一个非SwingU程中。我们创Z个匿名的ThreadQ用一个匿名的Runnable执行q个lookup?

private void searchButton_actionPerformed() {
 new Thread(){ 
  public void run() {
   lookupManager.lookup(searchTF.getText());
  }
 }.start();
} 

  q就完成了我们的SwingU程。简单地在actionPerformed()Ҏ中添加线E,保监听器在新的U程中执行照ֈ了整个线E问题。注意,我们不用处理像第一个例子那LM问题。通过把时间花费在定义一个事仉动的体系Q我们在和SwingU程相关处理上节U了更多的时间?

  l论

  如果你需要在同一个方法中执行大量的Swing代码和非Swing代码Q很Ҏ某些代码放错位|。事仉动的方式迫使你代码放在它应该在的地方Q-它仅应该在的地方。如果你在同一个方法中执行数据库调用和更新UIlgQ那么你在一个类中写入了太多的逻辑。分析你pȝ中的事gQ创建底层的事g模型迫使你代码放到正的地方。将Ҏ的数据库调用代码攑֜非UIcMQ也不要在非UIlg中更新UIlg。采用事仉动的体系QUI负责UI更新Q数据库理c负责数据库调用。在q一点上Q每一个封装的c都只用兛_自己的线E,不用担心pȝ其它部分如何动作。当Ӟ设计、构Z个事仉动的客户端也很有用,但是需要花费的旉代hq超q带来的l果pȝ的灵zL和可维护性的提高?






JAVA之\ 2006-05-20 23:59 发表评论
]]>JavaU程ȝ http://www.tkk7.com/xixidabao/archive/2006/05/08/44934.htmlJAVA之\JAVA之\Sun, 07 May 2006 16:42:00 GMThttp://www.tkk7.com/xixidabao/archive/2006/05/08/44934.html版權x,獲得授權轉載必須保留以下x和鏈?
作者的blog:(http://blog.matrix.org.cn/page/Kaizen)

在论坛上面常常看到初学者对U程的无可奈何,所以ȝZ下面一文章,希望对一些正在学习用javaU程的初学者有所帮助?br />
首先要理解线E首先需要了解一些基本的东西Q我们现在所使用的大多数操作pȝ都属于多dQ分时操作系l。正是由于这U操作系l的出现才有了多U程q个概念。我们用的windows,linux属于此列。什么是分时操作pȝ呢,通俗一点与是可以同一旉执行多个E序的操作系l,在自q电脑上面Q你是不是一边听歌,一边聊天还一边看|页呢?但实际上Qƈ不上cpu在同时执行这些程序,cpu只是时间切割ؓ旉片,然后时间片分配l这些程序,获得旉片的E序开始执行,不等执行完毕Q下个程序又获得旉片开始执行,q样多个E序轮流执行一D|_׃现在cpu的高速计能力,lh的感觉就像是多个E序在同时执行一栗?br />一般可以在同一旉内执行多个程序的操作pȝ都有q程的概?一个进E就是一个执行中的程?而每一个进E都有自q立的一块内存空?一l系l资?在进E概念中,每一个进E的内部数据和状态都是完全独立的.因此可以惛_创徏q执行一个进E的pȝ开像是比较大的Q所以线E出C。在java中,E序通过控制来执行E序?E序中单个顺序的控制称为线E?多线E则指的是在单个E序中可以同时运行多个不同的U程,执行不同的Q?多线E意味着一个程序的多行语句可以看上d乎在同一旉内同时运?Q你可以前面一句话的程序换成进E,q程是程序的一ơ执行过E?是系l运行程序的基本单位Q?br />
U程与进E相?是一D完成某个特定功能的代码,是程序中单个序的流控制;但与q程不同的是,同类的多个线E是׃n一块内存空间和一l系l资?而线E本w的数据通常只有微处理器的寄存器数据,以及一个供E序执行时用的堆栈.所以系l在产生一个线E?或者在各个U程之间切换?负担要比q程的?正因如此,U程也被UCؓ轻负药E?light-weight process).一个进E中可以包含多个U程.

多Q务是指在一个系l中可以同时q行多个E序,x多个独立q行的Q?每个d对应一个进E,同进E一?一个线E也有从创徏,q行到消亡的q程,UCؓU程的生命周?用线E的状?state)表明U程处在生命周期的哪个阶D?U程有创?可运?q行?d,M五中状?通过U程的控制与调度可ɾU程在这几种状态间转化每个E序臛_自动拥有一个线E?UCؓȝE?当程序加载到内存?启动ȝE?

[U程的运行机制以及调度模型]
java中多U程是一个类或一个程序执行或理多个U程执行d的能力,每个U程可以独立于其他线E而独立运行,当然也可以和其他U程协同q行Q一个类控制着它的所有线E,可以军_哪个U程得到优先U,哪个U程可以讉K其他cȝ资源Q哪个线E开始执行,哪个保持休眠状态?br />下面是线E的机制图:


U程的状态表C线E正在进行的zd以及在此旉D内所能完成的d.U程有创?可运?q行?d,M五中状?一个具有生命的U程,L处于q五U状态之一Q?br />1.创徏状?/b>
使用newq算W创Z个线E后,该线E仅仅是一个空对象,pȝ没有分配资源,U该U程处于创徏状?new thread)
2.可运行状?/b>
使用start()Ҏ启动一个线E后,pȝU程分配了除CPU外的所需资源,使该U程处于可运行状?Runnable)
3.q行中状?/b>
Javaq行pȝ通过调度选中一个Runnable的线E?使其占有CPUq{行中状?Running).此时,pȝ真正执行U程的run()Ҏ.
4.d状?/b>
一个正在运行的U程因某U原因不能l运行时,q入d状?Blocked)
5.M状?/b>
U程l束后是M状?Dead)

同一时刻如果有多个线E处于可q行状?则他们需要排队等待CPU资源.此时每个U程自动获得一个线E的优先U?priority),优先U的高低反映U程的重要或紧急程?可运行状态的U程按优先排队,U程调度依据优先U基上的"先到先服?原则.
U程调度理器负责线E排队和CPU在线E间的分?q由U程调度法q行调度.当线E调度管理器选种某个U程?该线E获得CPU资源而进入运行状?

U程调度是先占式调度,卛_果在当前U程执行q程中一个更高优先的线E进入可q行状?则这个线E立卌调度执行.先占式调度分?独占式和分时方式.
独占方式?当前执行U程一直执行下??到执行完毕或׃某种原因d攑ּCPU,或CPU被一个更高优先的线E抢?br />分时方式?当前q行U程获得一个时间片,旉到时,即没有执行完也要让出CPU,q入可运行状?{待下一个时间片的调?pȝ选中其他可运行状态的U程执行
分时方式的系l每个U程工作若干?实现多线E同时运?br />
另外h意下面的U程调度规则Q如果有不理解,不急,往下看Q:
①如果两个或是两个以上的U程都修改一个对象,那么把执行修改的Ҏ定义同步的(SynchronizedQ?如果对象更新影响到只L法,那么只度Ҏ也应该定义ؓ同步?br />②如果一个线E必ȝ待一个对象状态发生变化,那么它应该在对象内部{待Q而不是在外部{待Q它可以调用一个被同步的方法,q让q个Ҏ调用wait()
③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()ҎQ这l等待队列的U程提供Z来看一看执行环境是否已发生改变
④记住wait(),notify(),notifyAll()Ҏ属于Objectc,而不是Threadc,仔细查看是否每次执行wait()Ҏ都有相应的notify()或notifyAll()ҎQ且它们作用与相同的对象 在java中每个类都有一个主U程Q要执行一个程序,那么q个cd中一定要有mainҎQ这个manҎ也就是java class中的ȝE。你可以自己创徏U程Q有两种ҎQ一是承Threadc,或是实现Runnable接口。一般情况下Q最好避免承,因ؓjava中是单根l承Q如果你选用l承Q那么你的类失MҎ,当然也不能全然否定承Thread,该方法编写简?可以直接操作U程,适用于单重承情c至于选用那一U,具体情况具体分析?br />

eg.l承Thread
public class MyThread_1 extends Thread
{
public void run()
{
//some code
}
}


eg.实现Runnable接口
public class MyThread_2 implements Runnable
{
public void run()
{
//some code
}
}



当用承创建线E,q样启动U程Q?br />
new MyThread_1().start()


当用实现接口创建线E,q样启动U程Q?br />
new Thread(new MyThread_2()).start()


注意Q其实是创徏一个线E实例,q以实现了Runnable接口的类为参C入这个实例,当执行这个线E的时候,MyThread_2中run里面的代码将被执行?br />下面是完成的例子Q?br />
public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
new Thread(new MyThread()).start();
}
}



执行后将打印出:
My Name is Thread-0

你也可以创徏多个U程Q像下面q样
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();



那么会打印出Q?br />My Name is Thread-0
My Name is Thread-1
My Name is Thread-2


看了上面的结果,你可能会认ؓU程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是U程的执行机Ӟ影响U程执行序的因素有几点Q首先看看前面提到的优先U别


public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先
t1.start();
t2.start();
t3.start();
}
}


再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2



U程的优先分ؓ10U,分别??0的整C表,默认情况?。上面的t2.setPriority(Thread.MAX_PRIORITY){h与t2.setPriority(10Q?br />然后是线E程序本w的设计Q比如用sleep,yield,joinQwait{方法(详情LJDKDocument)

public class MyThread implements Runnable
{
public void run()
{
try
{
int sleepTime=(int)(Math.random()*100);//产生随机数字Q?br />Thread.currentThread().sleep(sleepTime);//让其休眠一定时_旉又上面sleepTime军_
//public static void sleep(long millis)throw InterruptedException QAPIQ?br />System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
}catch(InterruptedException ie)//׃U程在休眠可能被中断Q所以调用sleepҎ的时候需要捕捉异?br />{
ie.printStackTrace();
}
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t1.start();
t2.start();
t3.start();
}
}


执行后观察其输出Q?br />
Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69




上面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句Q当U程休眠的时候就会让出cpuQcpu会选择执行处于runnable状态中的其他线E,当然也可能出现这U情况,休眠的Thread立即q入了runnable状态,cpu再次执行它?br />[U程l概念]
U程是可以被l织的,java中存在线E组的概念,每个U程都是一个线E组的成?U程l把多个U程集成Z个对?通过U程l可以同时对其中的多个线E进行操?如启动一个线E组的所有线E等.Java的线E组由java.lang包中的Thread——Groupcd?
ThreadGroupcȝ来管理一l线E?包括:U程的数?U程间的关系,U程正在执行的操?以及U程要启动或终止时间等.U程l还可以包含U程l?在Java的应用程序中,最高层的线E组是名位main的线E组,在main中还可以加入U程或线E组,在mian的子U程l中也可以加入线E和U程l?形成U程l和U程之间的树状承关pR像上面创徏的线E都是属于mainq个U程l的?br />借用上面的例子,main里面可以q样写:

public static void main(String[] args)
{
/***************************************
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
***********************************/
ThreadGroup group1=new ThreadGroup("group1");
ThreadGroup group2=new ThreadGroup(group1,"group2");
Thread t1=new Thread(group2,new MyThread());
Thread t2=new Thread(group2,new MyThread());
Thread t3=new Thread(group2,new MyThread());
t1.start();
t2.start();
t3.start();
}



U程l的嵌套Qt1,t2,t3被加入group2,group2加入group1?br />另外一个比较多是关于U程同步斚w的,试想q样一U情况,你有一W存Ƒ֜银行Q你在一安行ؓ你的账户存款Q而你的妻子在另一安行从q个账户提款Q现在你?000块在你的账户里面。你存入?000Q但是由于另一方也在对q笔存款q行操作Qh家开始执行的时候只看到账户里面原来?000元,当你的妻子提?000元后Q你d所在的银行pZ的̎户里面没有钱了,而你所在的银行却认Zq有2000元?br />看看下面的例子:

class BlankSaving //储蓄账户
{
private static int money=10000;
public void add(int i)
{
money=money+i;
System.out.println("Husband 向银行存入了 [K?+i+"]");
}
public void get(int i)
{
money=money-i;
System.out.println("Wife 向银行取C [K?+i+"]");
if(money<0)
System.out.println("余额不Q?);
}
public int showMoney()
{
return money;
}
}


class Operater implements Runnable
{
String name;
BlankSaving bs;
public Operater(BlankSaving b,String s)
{
name=s;
bs=b;



}
public static void oper(String name,BlankSaving bs)
{



if(name.equals("husband"))
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.add(1000);
}
}catch(InterruptedException e){}
}else
{
try
{



for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.get(1000);
}
}catch(InterruptedException e){}
}
}
public void run()
{
oper(name,bs);
}
}
public class BankTest
{
public static void main(String[] args)throws InterruptedException
{
BlankSaving bs=new BlankSaving();
Operater o1=new Operater(bs,"husband");
Operater o2=new Operater(bs,"wife");
Thread t1=new Thread(o1);
Thread t2=new Thread(o2);
t1.start();
t2.start();
Thread.currentThread().sleep(500);
}



}



下面是其中一ơ的执行l果Q?br />


---------first--------------
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Husband 向银行存入了 [K?000]


看到了吗Q这可不是正的需求,在husbandq没有结束操作的时候,wife插了进来,q样很可能导致意外的l果。解军_法很单,是对数据q行操作Ҏ声明为synchronized,当方法被该关键字声明后,也就意味着Q如果这个数据被加锁Q只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也是当你存款的时候,q笔账户在其他地Ҏ不能q行操作的,只有你存Ƒ֮毕,银行理人员̎戯锁,其他人才能对q个账户q行操作?br />修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs)Q再看看l果:

Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Husband 向银行存入了 [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]
Wife 向银行取C [K?000]




当丈夫完成操作后Q妻子才开始执行操作,q样的话Q对׃n对象的操作就不会有问题了?br />[wait and notify]
你可以利用这两个Ҏ很好的控制线E的执行程Q当U程调用waitҎ后,U程被挂vQ直到被另一U程唤醒QnotifyQ或则是如果waitҎ指定有时间得话,在没有被唤醒的情况下Q指定时间时间过后也自动被唤醒。但是要注意一定,被唤醒ƈ不是指马上执行,而是从组塞状态变为可q行状态,其是否运行还要看cpu的调度?br />事例代码Q?br />
class MyThread_1 extends Thread
{
Object lock;
public MyThread_1(Object o)
{
lock=o;
}
public void run()
{
try
{
synchronized(lock)
{
System.out.println("Enter Thread_1 and wait");
lock.wait();
System.out.println("be notified");
}
}catch(InterruptedException e){}
}
}
class MyThread_2 extends Thread
{
Object lock;
public MyThread_2(Object o)
{
lock=o;
}
public void run()
{
synchronized(lock)
{
System.out.println("Enter Thread_2 and notify");
lock.notify();
}
}
}
public class MyThread
{
public static void main(String[] args)
{
int[] in=new int[0];//notice
MyThread_1 t1=new MyThread_1(in);
MyThread_2 t2=new MyThread_2(in);
t1.start();
t2.start();
}
}




执行l果如下Q?br />Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified


可能你注意到了在使用wait and notifyҎ得时候我使用了synchronized块来包装q两个方法,q是׃调用q两个方法的时候线E必获得锁Q也是上面代码中的lock[]Q如果你不用synchronized包装q两个方法的得话Q又或则锁不一是同一把,比如在MyThread_2中synchronized(lock)改ؓsynchronized(this),那么执行q个E序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notifyҎ是Object中的Qƈ不在Threadq个cM。最后你可能注意Cq点Qint[] in=new int[0];Z么不是创建new Object而是一?长度的数l,那是因ؓ在java中创Z?长度的数l来充当锁更加高效?br />
Thread作ؓjava中一重要l成部分Q当然还有很多地斚w要更深刻的认识,上面只是对Thread的一些常识和易错问题做了一个简要的ȝQ若要真正的掌握java的线E,q需要自己多做ȝ





JAVA之\ 2006-05-08 00:42 发表评论
]]>
վ֩ģ壺 ˳ѵӰ| ޹ۺ| ŷavۺɫ| ޾ƷþëƬ| һѿ| www.ձ| ˳ۺ | þ99Ʒ| aһƵ| һһһƵѿ| 뾫ƷAVӰ| 2018Ƶ| Ƶ߹ۿվ| ߹ۿƵ| av÷ۺ| ҹƷþþþþ| һһƬѲi| ŮˬƵѲ| ɫͼ.com| һAvëƬþþƷ | ڵ߹ۿƵ| ھƷһëƬѿ| AVպAV뵼| ַѹۿ | 97Ƶ| ĻƵƷһ| ޾Ʒþþþȥq| 3344߿Ƭ| þþƷ77777| AVɫ| Ƶ69| ɫѲ| AV˾Ʒպһ| avһ߲| ˿ҹëƬ| 99re6ƵƷѹۿ| 㽶һƵ߹ۿ| ޴߶ר| һ߹ۿ| ޾ƷƵۿ| VA߹ۿ|