隨筆 - 154  文章 - 60  trackbacks - 0
          <2007年11月>
          28293031123
          45678910
          11121314151617
          18192021222324
          2526272829301
          2345678

          聲明:

          該blog是為了收集資料,認識朋友,學習、提高技術,所以本blog的內(nèi)容除非聲明,否則一律為轉載!!

          感謝那些公開自己技術成果的高人們!!!

          支持開源,尊重他人的勞動!!

          常用鏈接

          留言簿(3)

          隨筆分類(148)

          隨筆檔案(143)

          收藏夾(2)

          其他

          學習(技術)

          觀察思考(非技術)

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          多線程編程——實戰(zhàn)篇(一)

          時間:2006-09-12
          作者:axman

          在進入實戰(zhàn)篇以前,我們簡單說一下多線程編程的一般原則。

            [安全性]是多線程編程的首要原則,如果兩個以上的線程訪問同一對象時,一個線程會損壞另一個線程的數(shù)據(jù),這就是違反了安全性原則,這樣的程序是不能進入實際應用的。

            安全性的保證可以通過設計安全的類和程序員的手工控制。如果多個線程對同一對象訪問不會危及安全性,這樣的類就是線程安全的類,在JAVA中比如String類就被設計為線程安全的類。而如果不是線程安全的類,那么就需要程序員在訪問這些類的實例時手工控制它的安全性。

            [可行性]是多線程編程的另一個重要原則,如果僅僅實現(xiàn)了安全性,程序卻在某一點后不能繼續(xù)執(zhí)行或者多個線程發(fā)生死鎖,那么這樣的程序也不能作為真正的多線程程序來應用。

            相對而言安全性和可行性是相互抵觸的,安全性越高的程序,可性行會越低。要綜合平衡。

            [高性能] 多線程的目的本來就是為了增加程序運行的性能,如果一個多線程完成的工作還不如單線程完成得快。那就不要應用多線程了。

            高性能程序主要有以下幾個方面的因素:

            數(shù)據(jù)吞吐率,在一定的時間內(nèi)所能完成的處理能力。

            響應速度,從發(fā)出請求到收到響應的時間。

            容量,指同時處理雅致同任務的數(shù)量。

            安全性和可行性是必要條件,如果達到不這兩個原則那就不能稱為真正的多線程程序。而高性是多線程編程的目的,也可以說是充要條件。否則,為什么采用多線程編程呢?

           

          [生產(chǎn)者與消費者模式]

            首先以一個生產(chǎn)者和消費者模式來進入實戰(zhàn)篇的第一節(jié)。

            生產(chǎn)者和消費者模式中保護的是誰?

            多線程編程都在保護著某些對象,這些個對象是"緊俏資源",要被最大限度地利用,這也是采用多線程方式的理由。在生產(chǎn)者消費者模式中,我們要保護的是"倉庫",在我下面的這個例子中,

          就是桌子(table)。

            我這個例子的模式完全是生產(chǎn)者-消費者模式,但我換了個名字。廚師-食客模式,這個食堂中只有1張桌子,同時最多放10個盤子,現(xiàn)在有4個廚師做菜,每做好一盤就往桌子上放(生產(chǎn)者將產(chǎn)品往倉庫中放),而有6個食客不停地吃(消費者消費產(chǎn)品,為了說明問題,他們的食量是無限的)。

            一般而言,廚師200-400ms做出一盤菜,而食客要400-600ms吃完一盤。當桌子上放滿了10個盤子后,所有廚師都不能再往桌子上放,而當桌子是沒有盤子時,所有的食客都只好等待。

            下面我們來設計這個程序:

            因為我們不知道具體是什么菜,所以叫它food:

           class Food{}
          

            然后是桌子,因為它要有序地放而且要有序地取(不能兩個食客同時爭取第三盤菜),所以我們擴展LinkedList,或者你用聚合把一個LinkedList作為屬性也能達到同樣的目的,例子中我是用

          繼承,從構造方法中傳入一個可以放置的最大值。

          class Table extends java.util.LinkedList{
          int maxSize;
          public Table(int maxSize){
          this.maxSize = maxSize;
          }
          }
          

          現(xiàn)在我們要為它加兩個方法,一是廚師往上面放菜的方法,一是食客從桌子上拿菜的方法。

          放菜:因為一張桌子由多個廚師放菜,所以廚師放菜的要被同步,如果桌子上已經(jīng)有十盤菜了。所有廚師就要等待:

           public synchronized void putFood(Food f){
          while(this.size() >= this.maxSize){
          try{
          this.wait();
          }catch(Exception e){}
          }
          this.add(f);
          notifyAll();
          }
          

          拿菜:同上面,如果桌子上一盤菜也沒有,所有食客都要等待:

           public synchronized Food getFood(){
          while(this.size() <= 0){
          try{
          this.wait();
          }catch(Exception e){}
          }
          Food f = (Food)this.removeFirst();
          notifyAll();
          return f;
          }
          

          廚師類:

            由于多個廚師要往一張桌子上放菜,所以他們要操作的桌子應該是同一個對象,我們從構造方法中將桌子對象傳進去以便控制在主線程中只產(chǎn)生一張桌子。

          廚師做菜要用一定的時候,我用在make方法中用sleep表示他要消耗和時候,用200加上200的隨機數(shù)保證時間有200-400ms中。做好后就要往桌子上放。

          這里有一個非常重要的問題一定要注意,就是對什么范圍同步的問題,因為產(chǎn)生競爭的是桌子,所以所有putFood是同步的,而我們不能把廚師自己做菜的時間也放在同步中,因為做菜是各自做的。同樣食客吃菜的時候也不應該同步,只有從桌子中取菜的時候是競爭的,而具體吃的時候是各自在吃。所以廚師類的代碼如下:

           class Chef extends Thread{
          Table t;
          Random r = new Random(12345);
          public Chef(Table t){
          this.t = t;
          }
          public void run(){
          while(true){
          Food f = make();
          t.putFood(f);
          }
          }
          private Food make(){
          try{
          Thread.sleep(200+r.nextInt(200));
          }catch(Exception e){}
          return new Food();
          }
          }
          

          同理我們產(chǎn)生食客類的代碼如下:

          class Eater extends Thread{
          Table t;
          Random r = new Random(54321);
          public Eater(Table t){
          this.t = t;
          }
          public void run(){
          while(true){
          Food f = t.getFood();
          eat(f);
          }
          }
          private void eat(Food f){
          try{
          Thread.sleep(400+r.nextInt(200));
          }catch(Exception e){}
          }
          }
          

          完整的程序在這兒:

          package debug;
          import java.util.regex.*;
          import java.util.*;
          class Food{}
          class Table extends LinkedList{
          int maxSize;
          public Table(int maxSize){
          this.maxSize = maxSize;
          }
          public synchronized void putFood(Food f){
          while(this.size() >= this.maxSize){
          try{
          this.wait();
          }catch(Exception e){}
          }
          this.add(f);
          notifyAll();
          }
          public synchronized Food getFood(){
          while(this.size() <= 0){
          try{
          this.wait();
          }catch(Exception e){}
          }
          Food f = (Food)this.removeFirst();
          notifyAll();
          return f;
          }
          }
          class Chef extends Thread{
          Table t;
          String name;
          Random r = new Random(12345);
          public Chef(String name,Table t){
          this.t = t;
          this.name = name;
          }
          public void run(){
          while(true){
          Food f = make();
          System.out.println(name+" put a Food:"+f);
          t.putFood(f);
          }
          }
          private Food make(){
          try{
          Thread.sleep(200+r.nextInt(200));
          }catch(Exception e){}
          return new Food();
          }
          }
          class Eater extends Thread{
          Table t;
          String name;
          Random r = new Random(54321);
          public Eater(String name,Table t){
          this.t = t;
          this.name = name;
          }
          public void run(){
          while(true){
          Food f = t.getFood();
          System.out.println(name+" get a Food:"+f);
          eat(f);
          }
          }
          private void eat(Food f){
          try{
          Thread.sleep(400+r.nextInt(200));
          }catch(Exception e){}
          }
          }
          public class Test {
          public static void main(String[] args) throws Exception{
          Table t = new Table(10);
          new Chef("Chef1",t).start();
          new Chef("Chef2",t).start();
          new Chef("Chef3",t).start();
          new Chef("Chef4",t).start();
          new Eater("Eater1",t).start();
          new Eater("Eater2",t).start();
          new Eater("Eater3",t).start();
          new Eater("Eater4",t).start();
          new Eater("Eater5",t).start();
          new Eater("Eater6",t).start();
          }
          }
          

           

          這一個例子中,我們主要關注以下幾個方面:

            1.同步方法要保護的對象,本例中是保護桌子,不能同時往上放菜或同時取菜。

            假如我們把putFood方法和getFood方法在廚師類和食客類中實現(xiàn),那么我們應該如此:

          (以putFood為例)

          class Chef extends Thread{
          Table t;
          String name;
          public Chef(String name,Table t){
          this.t = t;
          this.name = name;
          }
          public void run(){
          while(true){
          Food f = make();
          System.out.println(name+" put a Food:"+f);
          putFood(f);
          }
          }
          private Food make(){
          Random r = new Random(200);
          try{
          Thread.sleep(200+r.nextInt());
          }catch(Exception e){}
          return new Food();
          }
          public void putFood(Food f){//方法本身不能同步,因為它同步的是this.即Chef的實例
          synchronized (t) {//要保護的是t
          while (t.size() >= t.maxSize) {
          try {
          t.wait();
          }
          catch (Exception e) {}
          }
          t.add(f);
          t.notifyAll();
          }
          }
          }
          

            2.同步的范圍,在本例中是放和取兩個方法,不能把做菜和吃菜這種各自不相干的工作放在受保護的范圍中。

            3.參與者與容積比

             對于生產(chǎn)者和消費者的比例,以及桌子所能放置最多菜的數(shù)量三者之間的關系是影響性能的重要因素,如果是過多的生產(chǎn)者在等待,則要增加消費者或減少生產(chǎn)者的數(shù)據(jù),反之則增加生產(chǎn)者或減少消費者的數(shù)量。

            另外如果桌子有足夠的容量可以很大程序提升性能,這種情況下可以同時提高生產(chǎn)者和消費者的數(shù)量,但足夠大的容時往往你要有足夠大的物理內(nèi)存。


          =========================================================================
          多線程編程——實戰(zhàn)篇(二)

          時間:2006-11-21
          作者:axman

          本節(jié)繼續(xù)上一節(jié)的討論。

            [一個線程在進入對象的休息室(調(diào)用該對象的wait()方法)后會釋放對該對象的鎖],基于這個原因。在同步中,除非必要,否則你不應用使用Thread.sleep(long l)方法,因為sleep方法并不釋放對象的鎖。

            這是一個極其惡劣的品德,你自己什么事也不干,進入sleep狀態(tài),卻抓住競爭對象的監(jiān)視鎖不讓其它需要該對象監(jiān)視鎖的線程運行,簡單說是極端自私的一種行為。但我看到過很多程序員仍然有在同步方法中調(diào)用sleep的代碼。

            看下面的例子:

          package debug;
          class SleepTest{
          public synchronized void wantSleep(){
          try{
          Thread.sleep(1000*60);
          }catch(Exception e){}
          System.out.println("111");
          }
          public synchronized void say(){
          System.out.println("123");
          }
          }
          class T1 extends Thread{
          SleepTest st;
          public T1(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.wantSleep();
          }
          }
          class T2 extends Thread{
          SleepTest st;
          public T2(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.say();
          }
          }
          public class Test {
          public static void main(String[] args) throws Exception{
          SleepTest st = new SleepTest();
          new T1(st).start();
          new T2(st).start();
          }
          }
          

            我們看到,線程T1的實例運行后,當前線程抓住了st實例的鎖,然后進入了sleep。直到它睡滿60秒后才運行到System.out.println("111");然后run方法運行完成釋放了對st的監(jiān)視鎖,線程T2的實例才得到運行的機會。

            而如果我們把wantSleep方法改成:

            public synchronized void wantSleep(){
          try{
          //Thread.sleep(1000*60);
          this.wait(1000*60);
          }catch(Exception e){}
          System.out.println("111");
          }
          

            我們看到,T2的實例所在的線程立即就得到了運行機會,首先打印了123,而T1的實例所在的線程仍然等待,直到等待60秒后運行到System.out.println("111");方法。

            所以,調(diào)用wait(long l)方法不僅達到了阻塞當前線程規(guī)定時間內(nèi)不運行,而且讓其它有競爭需求的線程有了運行機會,這種利人不損己的方法,何樂而不為?這也是一個有良心的程序員應該遵循的原則。

            當一個線程調(diào)用wait(long l)方法后,線程如果繼續(xù)運行,你無法知道它是等待時間完成了還是在wait時被其它線程喚醒了,如果你非常在意它一定要等待足夠的時間才執(zhí)行某任務,而不希望是中途被喚醒,這里有一個不是非常準確的方法:

           long l = System.System.currentTimeMillis();
          wait(1000);//準備讓當前線程等待1秒
          while((System.System.currentTimeMillis() - l) < 1000)//執(zhí)行到這里說明它還沒有等待到1秒
          //是讓其它線程給鬧醒了
          wait(1000-(System.System.currentTimeMillis()-l));//繼續(xù)等待余下的時間.
          

            這種方法不是很準確,但基本上能達到目的。

            所以在同步方法中,除非你明確知道自己在干什么,非要這么做的話,你沒有理由使用sleep,wait方法足夠達到你想要的目的。而如果你是一個很保守的人,看到上面這段話后,你對sleep方法深惡痛絕,堅決不用sleep了,那么在非同步的方法中(沒有和其它線程競爭的對象),你想讓當前線程阻塞一定時間后再運行,應該如何做呢?(這完全是一種賣弄,在非同步的方法中你就應該合理地應用sleep嘛,但如果你堅決不用sleep,那就這樣來做吧)

              public static mySleep(long l){
          Object o = new Object();
          synchronized(o){
          try{
          o.wait(l);
          }catch(Exception e){}
          }
          }
          

            放心吧,沒有人能在這個方法外調(diào)用o.notify[All],所以o.wait(l)會一直等到設定的時間才會運行完成。

          [虛擬鎖的使用]

            虛擬鎖簡單說就是不要調(diào)用synchronized方法(它等同于synchronized(this))和不要調(diào)用synchronized(this),這樣所有調(diào)用在這個實例上的所有同步方法的線程只能有一個線程可以運行。也就是說:

            如果一個類有兩個同步方法 m1,m2,那么不僅是兩個以上線調(diào)用m1方法的線程只有一個能運行,就是兩個分別調(diào)用m1,m2的線程也只有一個能運行。當然非同步方法不存在任何競爭,在一個線程獲取該對象的監(jiān)視鎖后這個對象的非同步方法可以被任何線程調(diào)用。

            而大多數(shù)時候,我們可能會出現(xiàn)這種情況,多個線程調(diào)用m1時需要保護一種資源,而多個線程調(diào)用M2時要保護的是另一種資源,如果我們把m1,m2都設成同步方法。兩個分別調(diào)用這兩個方法的線程其實并不產(chǎn)生沖突,但它們都要獲取這個實例的鎖(同步方法是同步this)而產(chǎn)生了不必要競爭。

            所以這里應該采用虛擬鎖。

            即將m1和m2方法中各自保護的對象作為屬性a1,a2傳進來,然后將同步方法改為方法的同步塊分別以a1,a2為參數(shù),這樣到少是不同線程調(diào)用這兩個不同方法時不會產(chǎn)生競爭,當然如果m1,m2方法都操作同一受保護對象則兩個方法還是應該作為同步方法。這也是應該將方法同步還是采用同步塊的理由之一。

          package debug;
          class SleepTest{
          public synchronized void m1(){
          System.out.println("111");
          try{
          Thread.sleep(10000);
          }catch(Exception e){}
          }
          public synchronized void m2(){
          System.out.println("123");
          }
          }
          class T1 extends Thread{
          SleepTest st;
          public T1(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.m1();
          }
          }
          class T2 extends Thread{
          SleepTest st;
          public T2(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.m2();
          }
          }
          public class Test {
          public static void main(String[] args) throws Exception{
          SleepTest st = new SleepTest();
          new T1(st).start();
          new T2(st).start();
          }
          }
          

            這個例子可以看到兩個線程分別調(diào)用st實例的m1和m2方法卻因為都要獲取st的監(jiān)視鎖而產(chǎn)生了競爭。T2實例要在T1運行完成后才能運行(間隔了10秒)。而假設m1方法要操作操作一個文件 f1,m2方法要操作一個文件f2,當然我們可以在方法中分別同步f1,f2,但現(xiàn)在還不知道f2,f2是否存在,如果不存在我們就同步了一個null對象,那么我們可以使用虛擬鎖:

          package debug;
          class SleepTest{
          String vLock1 = "vLock1";
          String vLock2 = "vLock2";
          public void m1(){
          synchronized(vLock1){
          System.out.println("111");
          try {
          Thread.sleep(10000);
          }
          catch (Exception e) {}
          //操作f1
          }
          }
          public void m2(){
          synchronized(vLock2){
          System.out.println("123");
          //操作f2
          }
          }
          }
          class T1 extends Thread{
          SleepTest st;
          public T1(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.m1();
          }
          }
          class T2 extends Thread{
          SleepTest st;
          public T2(SleepTest st){
          this.st = st;
          }
          public void run(){
          st.m2();
          }
          }
          public class Test {
          public static void main(String[] args) throws Exception{
          SleepTest st = new SleepTest();
          new T1(st).start();
          new T2(st).start();
          }
          }
          

            我們看到兩個分別調(diào)用m1和m2的線程由于它們獲取不同對象的監(jiān)視鎖,它們沒有任何競爭就正常運行,只有這兩個線程同時調(diào)用m1或m2才會產(chǎn)生阻塞。



          =========================================================================
          多線程編程——實戰(zhàn)篇(三)

          時間:2006-12-28
          作者:axman

          [深入了解線程對象與線程,線程與運行環(huán)境]

            在基礎篇中的第一節(jié),我就強調(diào)過,要了解多線程編程,首要的兩個概念就是線程對象和線程。現(xiàn)在我們來深入理解線程對象,線程,運行環(huán)境之間的關系,弄清Runnable與Thread的作用。

           

            在JAVA平臺中,序列化機制是一個非常重要的機制,如果不能理解并熟練應用序列化機制,你就不能稱得上一個java程序員。

            在JAVA平臺中,為什么有些對象中可序列化的,而有些對象就不能序列化?

            能序列化的對象,簡單說是一種可以復制(意味著可以按一定機制進行重構它)的對象,這種對象說到底就是內(nèi)存中一些數(shù)據(jù)的組合。只要按一定位置和順序組合就能完整反映這個對象。

            而有些對象,是和當前環(huán)境相關的,它反映了當前運行的環(huán)境和時序,所以不能被序列,否則在另外的環(huán)境和時序中就無法“還原”。

            比如,一個Socket對象:

          Socket sc = new Socket("111.111.111.111",80);

            這個sc對象表示當前正在運行這段代碼的主機和IP為"111.111.111.111"的80端口之間建立的一個物理連結,如果它被序列化,那么在另一個時刻在另一個主機上它如何能被還原?Socket連結一旦斷開,就已經(jīng)不存在,它不可能在另一個時間被另一個主機所重現(xiàn)。重現(xiàn)的已經(jīng)不是原來那個sc對象了。

            線程對象也是這種不可序列化對象,當我們new Thread時,已經(jīng)初始化了當前這個線程對象所在有主機的運行環(huán)境相關的信息,線程調(diào)度機制,安全機制等只特定于當前運行環(huán)境的信息,假如它被序列化,在另一個環(huán)境中運行的時候原來初始化的運行環(huán)境的信息就不可能在新的環(huán)境中運行。而假如要重新初始化,那它已經(jīng)不是原來那個線程對象了。

            正如Socket封裝了兩個主機之間的連結,但它們并不是已經(jīng)連結關傳送數(shù)據(jù)了。要想傳送數(shù)據(jù),你還要getInputStream和getOutputStream,并read和write,兩臺主機之間才開始真正的“數(shù)據(jù)連結”。

            一個Thread對象并建立后,只是有了可以"運行"的令牌,僅僅只是一個"線程對象"。只有當它調(diào)用start()后,當前環(huán)境才會分配給它一個運行的"空間",讓這段代碼開始運行。這個運行的"空間",才叫真正的"線程"。也就是說,真正的線程是指當前正在執(zhí)行的那一個"事件"。是那個線程對象所在的運行環(huán)境。

            明白了上面的概念,我們再來看看JAVA中為什么要有Runnable對象和Thread對象。

            一、從設計技巧上說,JAVA中為了實現(xiàn)回調(diào),無法調(diào)用方法指針,那么利用接口來約束實現(xiàn)者強制提供匹配的方法,并將實現(xiàn)該接口的類的實例作為參數(shù)來提供給調(diào)用者,這是JAVA平臺實現(xiàn)回調(diào)的重要手段。

            二、但是從實際的操作來看,對于算法和數(shù)據(jù),是不依賴于任何環(huán)境的。所以把想要實現(xiàn)的操作中的算法和數(shù)據(jù)封裝到一個run方法中(由于算法本身是數(shù)據(jù)的一個部分,所以我把它們合并稱為數(shù)據(jù)),可以將離數(shù)據(jù)和環(huán)境的邏輯分離開來。使程序員只關心如何實現(xiàn)我想做的操作,而不要關心它所在的環(huán)境。當真正的需要運行的時候再將這段"操作"傳給一個具體當前環(huán)境的Thread對象。

            三、這是最最重要的原因:實現(xiàn)數(shù)據(jù)共享

            因為一個線程對象不對多次運行。所以把數(shù)據(jù)放在Thread對象中,不會被多個線程同時訪問。簡單說:

              class T extends Thread{
          Object x;
          public void run(){//......;}
          }
          T t = new T();
          

            當T的實例t運行后,t所包含的數(shù)據(jù)x只能被一個t.start();對象共享,除非聲明成    static Object x;

            一個t的實例數(shù)據(jù)只能被一個線程訪問。意思是"一個數(shù)據(jù)實例對應一個線程"。

            而假如我們從外部傳入數(shù)據(jù),比如

            class T extends Thread{
          private Object x;
          public T(Object x){
          this.x = x;
          }
          public void run(){//......;}
          }
          

            這樣我們就可以先生成一個x對象傳給多個Thread對象,多個線程共同操作一個數(shù)據(jù)。也就是"一個數(shù)據(jù)實例對應多個線程"。

            現(xiàn)在我們把數(shù)據(jù)更好地組織一下,把要操作的數(shù)據(jù)Object x和要進行的操作一個封裝到Runnable的run()方法中,把Runnable實例從外部傳給多個Thread對象。這樣,我們就有了:

            [一個對象的多個線程]

            這是以后我們要介紹的線程池的重要概念。


          ========================================================================
          多線程編程——實戰(zhàn)篇(四)

          時間:2007-02-08
          作者:axman


          不客氣地說,至少有一半人認為,線程的“中斷”就是讓線程停止。如果你也這么認為,那你對多線程編程還沒有入門。

            在java中,線程的中斷(interrupt)只是改變了線程的中斷狀態(tài),至于這個中斷狀態(tài)改變后帶來的結果,那是無法確定的,有時它更是讓停止中的線程繼續(xù)執(zhí)行的唯一手段。不但不是讓線程停止運行,反而是繼續(xù)執(zhí)行線程的手段。

            對于執(zhí)行一般邏輯的線程,如果調(diào)用它的interrupt()方法,那么對這個線程沒有任何影響,比如線程a正在執(zhí)行:while(條件) x ++;這樣的語句,如果其它線程調(diào)用a.interrupt();那么并不會影響a對象上運行的線程,如果在其它線程里測試a的中斷狀態(tài)它已經(jīng)改變,但并不會停止這個線程的運行。在一個線程對象上調(diào)用interrupt()方法,真正有影響的是wait,join,sleep方法,當然這三個方法包括它們的重載方法。

            請注意:[上面這三個方法都會拋出InterruptedException],記住這句話,下面我會重復。一個線程在調(diào)用interrupt()后,自己不會拋出InterruptedException異常,所以你看到interrupt()并沒有拋出這個異常,所以我上面說如果線程a正在執(zhí)行while(條件) x ++;你調(diào)用a.interrupt();后線程會繼續(xù)正常地執(zhí)行下去。

            但是,如果一個線程被調(diào)用了interrupt()后,它的狀態(tài)是已中斷的。這個狀態(tài)對于正在執(zhí)行wait,join,sleep的線程,卻改變了線程的運行結果。

            一、對于wait中等待notify/notifyAll喚醒的線程,其實這個線程已經(jīng)“暫停”執(zhí)行,因為它正在某一對象的休息室中,這時如果它的中斷狀態(tài)被改變,那么它就會拋出異常。這個InterruptedException異常不是線程拋出的,而是wait方法,也就是對象的wait方法內(nèi)部會不斷檢查在此對象上休息的線程的狀態(tài),如果發(fā)現(xiàn)哪個線程的狀態(tài)被置為已中斷,則會拋出InterruptedException,意思就是這個線程不能再等待了,其意義就等同于喚醒它了。

            這里唯一的區(qū)別是,被notify/All喚醒的線程會繼續(xù)執(zhí)行wait下面的語句,而在wait中被中斷的線程則將控制權交給了catch語句。一些正常的邏輯要被放到catch中來運行。但有時這是唯一手段,比如一個線程a在某一對象b的wait中等待喚醒,其它線程必須獲取到對象b的監(jiān)視鎖才能調(diào)用b.notify()[All],否則你就無法喚醒線程a,但在任何線程中可以無條件地調(diào)用a.interrupt();來達到這個目的。只是喚醒后的邏輯你要放在catch中,當然同notify/All一樣,繼續(xù)執(zhí)行a線程的條件還是要等拿到b對象的監(jiān)視鎖。

            二、對于sleep中的線程,如果你調(diào)用了Thread.sleep(一年);現(xiàn)在你后悔了,想讓它早些醒過來,調(diào)用interrupt()方法就是唯一手段,只有改變它的中斷狀態(tài),讓它從sleep中將控制權轉到處理異常的catch語句中,然后再由catch中的處理轉換到正常的邏輯。同樣地,于join中的線程你也可以這樣處理。

            對于一般介紹多線程模式的書上,他們會這樣來介紹:當一個線程被中斷后,在進入wait,sleep,join方法時會拋出異常。是的,這一點也沒有錯,但是這有什么意義呢?如果你知道那個線程的狀態(tài)已經(jīng)處于中斷狀態(tài),為什么還要讓它進入這三個方法呢?當然有時是必須這么做的,但大多數(shù)時候沒有這么做的理由,所以我上面主要介紹了在已經(jīng)調(diào)用這三個方法的線程上調(diào)用interrupt()方法讓它從"暫停"狀態(tài)中恢復過來。這個恢復過來就可以包含兩個目的:

            一、[可以使線程繼續(xù)執(zhí)行],那就是在catch語句中招待醒來后的邏輯,或由catch語句轉回正常的邏輯。總之它是從wait,sleep,join的暫停狀態(tài)活過來了。

            二、[可以直接停止線程的運行],當然在catch中什么也不處理,或return,那么就完成了當前線程的使命,可以使在上面“暫停”的狀態(tài)中立即真正的“停止”。

            中斷線程

            有了上一節(jié)[線程的中斷],我們就好進行如何[中斷線程]了。這絕對不是玩一個文字游戲。是因為“線程的中斷”并不能保證“中斷線程”,所以我要特別地分為兩節(jié)來說明。這里說的“中斷線程”意思是“停止線程”,而為什么不用“停止線程”這個說法呢?因為線程有一個明確的stop方法,但它是反對使用的,所以請大家記住,在java中以后不要提停止線程這個說法,忘記它!但是,作為介紹線程知識的我,我仍然要告訴你為什么不用“停止線程”的理由。

            [停止線程]

            當在一個線程對象上調(diào)用stop()方法時,這個線程對象所運行的線程就會立即停止,并拋出特殊的ThreadDeath()異常。這里的“立即”因為太“立即”了,就象一個正在擺弄自己的玩具的孩子,聽到大人說快去睡覺去,就放著滿地的玩具立即睡覺去了。這樣的孩子是不乖的。

            假如一個線程正在執(zhí)行:

          synchronized void {
          x = 3;
          y = 4;
          }
          

            由于方法是同步的,多個線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執(zhí)行到x = 3;時,被調(diào)用了 stop()方法,即使在同步塊中,它也干脆地stop了,這樣就產(chǎn)生了不完整的殘廢數(shù)據(jù)。而多線程編程中最最基礎的條件要保證數(shù)據(jù)的完整性,所以請忘記線程的stop方法,以后我們再也不要說“停止線程”了。

             如何才能“結束”一個線程?

            [中斷線程]

            結束一個線程,我們要分析線程的運行情況。也就是線程正在干什么。如果那個孩子什么事也沒干,那就讓他立即去睡覺。而如果那個孩子正在擺弄他的玩具,我們就要讓它把玩具收拾好再睡覺。

            所以一個線程從運行到真正的結束,應該有三個階段:

          1. 正常運行.
          2. 處理結束前的工作,也就是準備結束.
          3. 結束退出.

            在我的JDBC專欄中我N次提醒在一個SQL邏輯結束后,無論如何要保證關閉Connnection那就是在finally從句中進行。同樣,線程在結束前的工作應該在finally中來保證線程退出前一定執(zhí)行:

           try{
          正在邏輯
          }catch(){}
          finally{
          清理工作
          }
          

            那么如何讓一個線程結束呢?既然不能調(diào)用stop,可用的只的interrupt()方法。但interrupt()方法只是改變了線程的運行狀態(tài),如何讓它退出運行?對于一般邏輯,只要線程狀態(tài)已經(jīng)中斷,我們就可以讓它退出,所以這樣的語句可以保證線程在中斷后就能結束運行:

           while(!isInterrupted()){
          正常邏輯
          }
          

            這樣如果這個線程被調(diào)用interrupt()方法,isInterrupted()為true,就會退出運行。但是如果線程正在執(zhí)行wait,sleep,join方法,你調(diào)用interrupt()方法,這個邏輯就不完全了。

            如果一個有經(jīng)驗的程序員來處理線程的運行的結束:

           public void run(){
          try{
          while(!isInterrupted()){
          正常工作
          }
          }
          catch(Exception e){
          return;
          }
          finally{
          清理工作
          }
          }
          

            我們看到,如果線程執(zhí)行一般邏輯在調(diào)用innterrupt后,isInterrupted()為true,退出循環(huán)后執(zhí)行清理工作后結束,即使線程正在wait,sleep,join,也會拋出異常執(zhí)行清理工作后退出。

            這看起來非常好,線程完全按最我們設定的思路在工作。但是,并不是每個程序員都有這種認識,如果他聰明的自己處理異常會如何?事實上很多或大多數(shù)程序員會這樣處理:

           public void run(){
          while(!isInterrupted()){
          try{
          正常工作
          }catch(Exception e){
          //nothing
          }
          finally{
          }
          }
          }
          }
          

            想一想,如果一個正在sleep的線程,在調(diào)用interrupt后,會如何?wait方法檢查到isInterrupted()為true,拋出異常,而你又沒有處理。而一個拋出了InterruptedException的線程的狀態(tài)馬上就會被置為非中斷狀態(tài),如果catch語句沒有處理異常,則下一次循環(huán)中isInterrupted()為false,線程會繼續(xù)執(zhí)行,可能你N次拋出異常,也無法讓線程停止。

            那么如何能確保線程真正停止?在線程同步的時候我們有一個叫“二次惰性檢測”(double check),能在提高效率的基礎上又確保線程真正中同步控制中。那么我把線程正確退出的方法稱為“雙重安全退出”,即不以isInterrupted()為循環(huán)條件。而以一個標記作為循環(huán)條件:

          class MyThread extend Thread{
          private boolean isInterrupted = false;//這一句以后要修改
          public void interrupt(){
          isInterrupted = true;
          super.interrupt();
          }
          public void run(){
          while(!isInterrupted){
          try{
          正常工作
          }catch(Exception e){
          //nothing
          }
          finally{
          }
          }
          }
          }
          

            試試這段程序,可以正確工作嗎?

            對于這段程序仍然還有很多可說的地方,先到這里吧。


          =======================================================================
          http://dev2dev.bea.com.cn/bbsdoc/20070208338913.html
          posted on 2007-11-09 13:44 lk 閱讀(351) 評論(0)  編輯  收藏 所屬分類: j2se
          主站蜘蛛池模板: 高平市| 榕江县| 库尔勒市| 汉中市| 彭泽县| 海盐县| 桓台县| 大洼县| 襄垣县| 荥经县| 开封市| 铅山县| 江城| 桐梓县| 柳林县| 昌都县| 太仆寺旗| 三原县| 云林县| 漳州市| 大关县| 繁昌县| 遂溪县| 鲁甸县| 延长县| 保德县| 高碑店市| 漳平市| 南阳市| 鄄城县| 鹿邑县| 邵武市| 万安县| 阿尔山市| 蛟河市| 顺平县| 延长县| 恩平市| 乌拉特前旗| 蒙自县| 宝山区|