基礎篇

hkmexu發表於2009-11-23

基礎篇 (一)

[寫在前面]
    隨著計算機技術的發展,程式設計模型也越來越複雜多樣化.但多執行緒程式設計模型是目前計算機
系統架構的最終模型.隨著CPU主頻的不斷攀升,X86架構的硬體已經成為瓶,在這種架構的CPU主頻最
高為4G.事實上目前3.6G主頻的CPU已經接近了頂峰.
    如果不能從根本上更新當前CPU的架構(在很長一段時間內還不太可能),那麼繼續提高CPU
效能的方法就是超執行緒CPU模式.
    那麼,作業系統,應用程式要發揮CPU的最大效能,就是要改變到以多執行緒程式設計模型為主的並
行處理系統和併發式應用程式.
    所以,掌握多執行緒程式設計模型,不僅是目前提高應用效能的手段,更是下一代程式設計模型的核心
思想.
    多執行緒程式設計的目的,就是"最大限度地利用CPU資源",當某一執行緒的處理不需要佔用CPU而只
和I/O,OEMBIOS等資源打交道時,讓需要佔用CPU資源的其它執行緒有機會獲得CPU資源
.從根本上說,這就是多
執行緒程式設計的最終目的.



[第一需要弄清的問題]
    如同程式和程式的區別,要掌握多執行緒程式設計,第一要弄清的問題是:執行緒物件和執行緒的區別.

    執行緒物件是可以產生執行緒的物件.比如在java平臺中Thread物件,Runnable物件.
    執行緒,是指正在執行的一個指點令序列.在java平臺上是指從一個執行緒物件的start()開始.
執行run方法體中的那一段相對獨立的過程.
    
    鑑於作者的水平,無法用更確切的詞彙來描述它們的定義.但這兩個有本質區別的概念請初
學者細細體會,隨著介紹的深入和例程分析的增加,就會慢慢明白它們所代表的真實含義.

    天下難事必始於易,天下大事必始於細.

    讓我們先從最簡單的"單執行緒"來入手.(1)帶引號說明只是相對而言的單執行緒.(2)基於java

    class BeginClass{
        public static void main(String[] args){
            for(int i=0;i<100;i++)
                System.out.println("Hello,World!");
        }
    }


    如果我們成功編譯了該java檔案,然後在命令列上敲入:

    java BeginClass
    現在發生了什麼呢?每一個java程式設計師,從他開始學習java的第一分鐘裡都會接觸到這個問
題,但是,你知道它到底發生發什麼?

    JVM程式被啟動,在同一個JVM程式中,有且只有一個程式,就是它自己.然後在這個JVM環境中,
所有程式的執行都是以執行緒來執行.JVM最先會產生一個主執行緒,由它來執行指定程式的入口點.在這個
程式中,就是主執行緒從main方法開始執行.當main方法結束後,主執行緒執行完成.JVM程式也隨之退出.
    我們看到的是一個主執行緒在執行main方法,這樣的只有一個執行緒執行程式邏輯的流程我們稱
之為單執行緒.這是JVM提供給我們的單執行緒環境,事實上,JVM底層還至少有垃圾回收這樣的後臺執行緒以
及其它非java執行緒,但這些執行緒結我們而言不可訪問,我們只認為它是單執行緒的.

    主執行緒是JVM自己啟動的,在這裡它不是從執行緒物件產生的.在這個執行緒中,它執行了main方法
這個指令序列.理解它,但它沒有更多可以研究的內容.

    [接觸多執行緒]

    class MyThread extends Thread{
        public void run(){
            System.out.println("Thread say:Hello,World!");
        }
    }

    public class MoreThreads{
        public static void main(String[] args){
            new MyThread();
            new MyThread().start();
            System.out.println("Main say:Hello,World");
        }
    }
    
    執行這個程式,main方法第一行產生了一個執行緒物件,但並沒有執行緒啟動.
    main方法第二行產生了一個執行緒物件,並啟動了一個執行緒.
    main方法第三行,產生並啟動一個執行緒後,主執行緒自己也繼續執行其它語句.

    我們先不研究Thread物件的具體內容,稍微來回想一下上面的兩個概念,執行緒物件和執行緒.
    在JAVA中,執行緒物件是JVM產生的一個普通的Object子類.
    而執行緒是CPU分配給這個物件的一個執行過程.我們說的這個執行緒在幹什麼,不是說一個線
程物件在幹什麼,而是這個執行過程在幹什麼.
如果一時想不明白,不要急,但你要記得它們不是一回
事就行了.
    
    累了吧?為不麼不繼續了?
    基於這種風格來介紹多執行緒,並不是每個人都喜歡和接受的,如果你不喜歡,正好不浪費你的
時間了,而如果你接受的話,那就看下一節吧.

 

基礎篇(二)

在進入java平臺的執行緒物件之前,基於基礎知識(一)的一些問題,我先插入兩個基本概念.

[執行緒的併發與並行]

在單CPU系統中,系統排程在某一時刻只能讓一個執行緒執行,雖然這種除錯機制有多種形式
(大多數是時間片輪巡為主),但無論如何,要通過不斷切換需要執行的執行緒讓其執行的方式
就叫併發(concurrent).

而在多CPU系統中,可以讓兩個以上的執行緒同時執行,這種可以同時讓兩個以上執行緒同時執行
的方式叫做
並行(parallel).

在上面包括以後的所有論述中,請各位朋友諒解,我無法用最準確的詞語來定義儲如併發和
並行這類術語,但我以我的經驗能通俗地告訴大家它是怎麼一回事,如果您看到我說的一些
"標準"文件上說的不一樣,只要意思一致,那您就不要挑刺了.

[JAVA執行緒物件]

    現在我們來開始考察JAVA中執行緒物件.
    在JAVA中,要開始一個執行緒,有兩種方式.一是直接呼叫Thread例項的start()方法,二是
將Runable例項傳給一個Thread例項然後呼叫它的start()方法.

    在基礎知識(一)中已經說過,執行緒物件和執行緒是兩個完全不同的概念.這裡我們再次
深入一下,生成一個執行緒的例項,並不代表啟動了執行緒.而啟動執行緒是說在某個執行緒物件上啟動
了該例項對應的執行緒,當該執行緒結束後,並不會就立即消失.

    對於從很多書籍上可以看到的基礎知識我就不用多說了.既然是基礎知識,我也著重
於從普通文件上讀不到的內容.
    所以本節我重點要說的是兩種執行緒物件產生執行緒方式的區別.


class MyThread extends Thread{
  public int x = 0;
  
  public void run(){
    
    for(int i=0;i<100;i++){
      try{
        Thread.sleep(10);
      }catch(Exception e){}
      System.out.println(x++);
      
    }
  }
}

如果我們生成MyThread的一個例項,然後呼叫它的start();方法,那麼就產生了這個例項對應
的執行緒:

public class Test {
  public static void main(String[] args) throws Exception{
    MyThread mt = new MyThread();
    mt.start();
  }
}

    
    不用說,最終會列印出0到99,現在我們稍微玩一點花樣:

public class Test {
  public static void main(String[] args) throws Exception{
    MyThread mt = new MyThread();
    mt.start();
    System.out.println(101);
  }
}

    也不用說,在基礎知識(一)中我們知道由於單CPU的原因,一般會先列印101,然後列印
0到99.不過我們可以控制執行緒讓它按我們的意思來執行:

public class Test {
  public static void main(String[] args) throws Exception{
    MyThread mt = new MyThread();
    mt.start();
    mt.join();
    System.out.println(101);
  }
}

好了,我們終於看到,mt例項對應的執行緒(假如我有時說mt執行緒請你不要怪我,不過我儘量不這麼說)
在執行完成後,主執行緒才列印101.因為我們讓主當前執行緒(這裡是主執行緒)等待mt執行緒的執行結束.
"線上程物件mt上呼叫join()方法,就是讓當前正在執行的執行緒(執行mt.join這條語句的執行緒)等待執行緒物件

mt對應的執行緒執行完成後才繼續執行." 請大家一定要深刻理解並熟記這句話,而我這裡引出這個知識點的目

的是為了讓你繼續看下面的例子:

public class Test {
  public static void main(String[] args) throws Exception{
    MyThread mt = new MyThread();
    mt.start();
    mt.join();
    Thread.sleep(3000);
    mt.start();
  }
}

當執行緒物件mt執行完成後,我們讓主執行緒休息一下,然後我們再次在這個執行緒物件上啟動執行緒.結果我
們看到:

Exception in thread "main" java.lang.IllegalThreadStateException

也就是這種執行緒物件一時執行一次完成後,它就再也不能執行第二次了.
我們可以看一下它有具體實現:
    public synchronized void start() {
        if (started)
            throw new IllegalThreadStateException();
        started = true;
        group.add(this);
        start0();
    }

一個Thread的例項一旦呼叫start()方法,這個例項的started標記就標記為true,事實中不管這個執行緒
後來有沒有執行到底,只要呼叫了一次start()就再也沒有機會再次執行了,這意味著:

[通過Thread例項的start(),一個Thread的例項只能產生一個執行緒]

那麼如果要在一個例項上產生多個執行緒(也就是我們常說的執行緒池),我們應該如何做呢?這就是Runnable
介面給我們帶來的偉大的功能.(注:不過執行緒池還是和這裡的例子有小許差別,這裡可以說是讓一個程式碼段被多個執行緒呼叫,執行緒池確實也是這樣,但是執行緒池呼叫的程式碼段一定是一個迴圈執行程式碼段,不然程式碼段一跑完執行緒池裡所有的執行緒也就結束了。而引入執行緒池的本意是為了複用執行緒,所以執行緒池執行的那段程式碼必定是迴圈執行。實際中執行緒池是利用了生產者消費者模式,把要處理的資料封裝成一個個任務放入一個任務處理佇列。每個執行緒迴圈不停的從佇列中取任務處理。)

class R implements Runnable{
  private int x = 0;
  public void run(){

    for(int i=0;i<100;i++){
      try{
        Thread.sleep(10);
      }catch(Exception e){}
      System.out.println(x++);

    }
  }
}


正如它的名字一樣,Runnable的例項是可執行的,但它自己並不能直接執行,它需要被Thread物件來
包裝才行執行:

public class Test {
  public static void main(String[] args) throws Exception{
    new Thread(new R()).start();
  }
}

當然這個結果和mt.start()沒有什麼區別.但如果我們把一個Runnable例項給Thread物件多次包裝,我
們就可以看到它們實際是在同一例項上啟動執行緒:

public class Test {
  public static void main(String[] args) throws Exception{
    R r = new R();
    for(int i=0;i<10;i++)
      new Thread(r).start();
  }
}

x是例項物件,但結果是x被加到了999,說明這10個執行緒是在同一個r物件上執行的.請大家注意,因為這個
例子是在單CPU上執行的,所以沒有對多個執行緒同時操作共同的物件進行同步.這裡是為了說明的方便而
簡化了同步,而真正的環境中你無法預知程式會在什麼環境下執行,所以一定要考慮同步.


到這裡我們做一個完整的例子來說明執行緒產生的方式不同而生成的執行緒的區別:

package debug;

import java.io.*;
import java.lang.Thread;


class MyThread extends Thread{
  public int x = 0;

  public void run(){
    System.out.println(++x);
  }
}

class R implements Runnable{
  private int x = 0;
  public void run(){
    System.out.println(++x);
  }
}

public class Test {
  public static void main(String[] args) throws Exception{
    
    for(int i=0;i<10;i++){
      Thread t = new MyThread();
      t.start();
    }
    Thread.sleep(10000);//讓上面的執行緒執行完成
    R r = new R();
    for(int i=0;i<10;i++){
      Thread t = new Thread(r);
      t.start();
    }
  }
}

上面10個執行緒物件產生的10個執行緒執行時列印了10次1.
下面10個執行緒物件產生的10個執行緒執行時列印了1到10.

我們把下面的10個執行緒稱為同一例項(Runnable例項)的多個執行緒.

下節我們將研究執行緒物件方法,還是那句話,一般文件中可以讀到的內容我不會介紹太多
請大家自己瞭解.

 

基礎篇(三)

執行緒物件的幾個重要的方法

儘管執行緒物件的常用方法可以通過API文件來了解,但是有很多方法僅僅從API說明是無法詳細瞭解的.

本來打算用一節的篇幅來把執行緒方法中一些重要的知識說完,但這樣下來估計要很常的篇幅,可能要用
好幾節才能說把和執行緒方法相關的一些重要的知識說完.


首先我們接基礎篇(二)來說明start()方法.

一個執行緒物件生成後,如果要產生一個執行的執行緒,就一定要呼叫它的start()方法.在介紹這個方法時
不得不同時說明run方法.其實執行緒物件的run方法完全是一個介面回撥方法,它是你這個執行緒物件要完
成的具體邏輯.簡單說你要做什麼就你在run中完成,而如何做,什麼時候做就不需要你控制了,你只要調
用start()方法,JVM就會管理這個執行緒物件讓它產生一個執行緒並註冊到執行緒處理系統中.


從表面上看,start()方法呼叫了run()方法,事實上,start()方法並沒有直接呼叫run方法.在JDK1.5以前
start()方法是本地方法,它如何最終呼叫run方法已經不是JAVA程式設計師所能瞭解的.而在JDK1.5中,原來的
那個本地start()方法被start0()代替,另個一個純JAVA的start()中呼叫本地方法start0(),而在start()
方法中做了一個驗證,就是對一個全域性變數(物件變數)started做檢驗,如果為true,則start()丟擲異常,不
會呼叫本地方法start0(),否則,先將該變數設有true,然後呼叫start0()

從中我們可以看到這個為了控制一個執行緒物件只能執行成功一次start()方法.這是因為執行緒的執行要獲
取當前環境,包括安全,父執行緒的許可權,優先順序等條件,如果一個執行緒物件可以執行多次,那麼定義一個static
的執行緒在一個環境中獲取相應許可權和優先順序,執行完成後它在另一個環境中利用原來的許可權和優先順序等屬
性在當前環境中執行,這樣就造成無法預知的結果.簡單說來,一個執行緒物件只能成功執行一次,是基於
對執行緒管理的需要.

start()&run()區別
start()方法最本質的功能是從CPU中申請另一個執行緒空間來執行run()方法中的程式碼,它和當前的執行緒是
兩條線,在相對獨立的執行緒空間執行,也就是說,如果你直接呼叫執行緒物件的run()方法,當然也會執行,但
那是在當前執行緒中執行,run()方法執行完成後繼續執行下面的程式碼.而呼叫start()方法後,run()方法的
程式碼會和當前執行緒併發(單CPU)或並行(多CPU)執行.

所以請記住一句話[呼叫執行緒物件的run方法不會產生一個新的執行緒],雖然可以達到相同的執行結果,但執
行過程和執行效率不同.


[執行緒的interrupt()方法,interrupted()和isInterrupted()]

這三個方法是關係非常密切而且又比較複雜的,雖然它們各自的功能很清楚,但它們之間的關係有大多數
人不是真正的瞭解.

先說interrupt()方法,它是例項方法,而它也是最奇怪的方法,在java語言中,執行緒最初被設計為"隱晦難懂"
的東西,直到現在它的語義不沒有象它的名字那樣準確.
    大多數人以為,一個執行緒象呼叫了interrupt()方法,那它對應的執行緒就應該被中斷而丟擲異常,
事實中,當一個執行緒物件呼叫interrupt()方法,它對應的執行緒並沒有被中斷,只是改變了它的中斷狀態,使當前執行緒的狀態變成中斷狀態,如果沒有其它影響,執行緒還會自己繼續執行.
    只有當執行緒執行到sleep,wait,join等方法時,或者自己檢查中斷狀態而丟擲異常的情況下,執行緒
才會丟擲異常.

    如果執行緒物件呼叫interrupt()後它對應的執行緒就立即中斷,那麼interrupted()方法就不可能執行.因為interrupted()方法是一個static方法,就是說只能在當前執行緒上呼叫,而如果一個執行緒interrupt()後它已經中斷了,那它又如何讓自己interrupted()?(反面證明interrupt()不會使得執行緒立即中斷
    
    正因為一個執行緒呼叫interrupt()後只是改變了中斷狀態,它可以繼續執行下去,在沒有呼叫sleep,wait,join等法或自己丟擲異常之前,它就可以呼叫interrupted()來清除中斷狀態(恢復原狀)


    interrupted()方法會檢查當前執行緒的中斷狀態,如果為"被中斷狀態"則改變當前執行緒為"非中斷狀態"並返回true,如果為"非中斷狀態"則返回false,它不僅檢查當前執行緒是否為中斷狀態,而且在保證當前執行緒回來非中斷狀態,所以它叫"interrupted",是說中斷的狀態已經結束(到非中斷狀態了)

    isInterrupted()方法則僅僅檢查執行緒物件對應的執行緒是否是中斷狀態,並不改變它的狀態.
    
    目前大家只能先記住這三個方法的功能,只有真正深入到多執行緒程式設計實踐中,才會體會到它們為什麼
是物件方法,為什麼是類方法.

    執行緒到底什麼時候才被中斷丟擲InterruptedException異常,我們將在提高篇中詳細討論.

[sleep(),join(),yield()方法] 不能讓別人睡,不能讓別人放棄,能讓別人插隊-----Java是個好同志
    
    在現在的環節中,我只能先說明這些方法的作用和呼叫原則,至於為什麼,在基礎篇中無法深入,只能
在提高篇中詳細說明.
    
    sleep()方法中是類方法(Thread的static方法,不能讓別人睡),也就是對當前執行緒而言的,程式設計師不能指定某個執行緒去sleep,只能是當前執行緒執行到sleep()方法時,睡眠指定的時間(讓其它執行緒執行).事實上也只能是類方法,在當前執行緒上呼叫.試想如果你呼叫一個執行緒物件的sleep()方法,那麼這個物件對應的執行緒如果不是正在執行,它如何sleep()? 所以只有當前執行緒,因為它正在執行,你才能保證它可以呼叫sleep()方法.

    原則:[在同步方法中儘量不要呼叫執行緒的sleep()方法],或者簡單說,對於一般水平的程式設計師你基本
不應該呼叫sleep()方法.

    join()方法(Thread的非static方法,能讓別人插隊),正如第一節所言,在一個執行緒物件上呼叫join方法,是當前執行緒等待這個執行緒物件對應的執行緒結束,比如有兩個工作,工作A要耗時10秒鐘,工作B要耗時10秒或更多.我們在程式中先生成一個執行緒去做工作B,然後做工作A.

new B().start();//做工作B
A();//做工作A

工作A完成後,下面要等待工作B的結果來進行處理.如果工作B還沒有完成我就不能進行下面的工作C,所以

B b = new B();
b.start();//做工作B
A();//做工作A
b.join();//等工作B完成.
C();//繼續工作C.


    原則:[join是測試其它工作狀態的唯一正確方法],我見過很多人,甚至有的是博士生,在處理一項工作
時如果另一項工作沒有完成,說讓當前工作執行緒sleep(x),我問他,你這個x是如何指定的,你怎麼知道是100毫秒
而不是99毫秒或是101毫秒?其實這就是OnXXX事件的實質,我們不是要等多長時間才去做什麼事,而是當等待的
工作正好完成的時候去做.

    yield()方法也是類方法(Thread的static方法,不能讓別人放棄),只在當前執行緒上呼叫,理由同上,它主是讓當前執行緒放棄本次分配到的時間片原則:[不是非常必要的情況下,沒有理由呼叫它].呼叫這個方法不會提高任何效率,只是降低了CPU的總週期


上面介紹的執行緒一些方法,基於(基礎篇)而言只能簡單提及.以後具體應用中我會結合例項詳細論述.

執行緒本身的其它方法請參看API文件.下一節介紹非執行緒的方法,但和執行緒密切相關的兩[三]個物件方法:
[wait(),notify()/notifyAll()]

這是在多執行緒中非常重要的方法.

 

基礎篇(四)

    [wait(),notify()/notityAll()方法]

    關於這兩個方法,有很多的內容需要說明.在下面的說明中可能會有很多地方不能一下子
明白,但在看完本節後,即使不能完全明白,你也一定要回過頭來記住下面的兩句話:

[wait(),notify()/notityAll()方法是普通物件的方法(Object超類中實現),而不是執行緒物件的方法]

[wait(),notify()/notityAll()方法只能在同步方法中呼叫(因為這三個方法都是和當前物件的鎖相關,而同步方法的鎖就是當前物件的鎖)]


    [執行緒的互斥控制]
    
    多個執行緒同時操作某一物件時,一個執行緒對該物件的操作可能會改變其狀態,而該狀態會影響
另一執行緒對該物件的真正結果.
    這個例子我們在太多的文件中可以看到,就象兩個操售票員同時售出同一張票一樣.

    執行緒A                                        執行緒B

1.執行緒A在資料庫中查詢存票,發現票C可以賣出
2.執行緒A接受使用者訂票請求,準備出票.
3.                                                  這時切換到了執行緒B執行
4.                                                  執行緒B在資料庫中查詢存票,發現票C可以賣出                        
5.                                                  執行緒B將票賣了出去  
6.切換到執行緒A執行,執行緒A賣了一張已經賣出的票

所以需要一種機制來管理這類問題的發生,當某個執行緒正在執行一個不可分割的部分時,其它執行緒不能
不能同時執行這一部分.

象這種控制某一時刻只能有一個執行緒執行某個執行單元的機制就叫互斥控制或共享互斥(mutual exclusion)

    在JAVA中,用synchornized關鍵字來實現互斥控制(暫時這樣認為,JDK1.5已經發展了新的機制)

    [synchornized關鍵字]
    把一個單元宣告為synchornized,就可以讓在同一時間只有一個執行緒操作該方法.
    
    有人說synchornized就是一把鎖,事實上它確實存在鎖,但是是誰的鎖,鎖誰,這是一個非常複雜的
問題.

    每個物件只有一把監視鎖(monitor lock),一次只能被一個執行緒獲取.當一個執行緒獲取了這一個鎖
後,其它執行緒就只能等待這個執行緒釋放鎖才能再獲取.

    那麼synchornized關鍵字到底鎖什麼?得到了誰的鎖?
    
    對於同步塊,synchornized獲取的是引數中的物件鎖:

    synchornized(obj){
        //...............
    }
    執行緒執行到這裡時,首先要獲取obj這個例項的鎖,如果沒有獲取到執行緒只能等待.如果多個執行緒
執行到這裡,只能有一個執行緒獲取obj的鎖,然後執行{}中的語句,所以,obj物件的作用範圍不同,控制程式
不同.
    
    假如:
    public void test(){
        Object o = new Object();
        
        synchornized(obj){
            //...............
        }
    }

    這段程式控制不了任何,多個執行緒之間執行到Object o = new Object();時會各自產生一個物件
然後獲取這個物件有監視鎖,各自皆大歡喜地執行.

    而如果是類的屬性:

    class Test{
        Object o = new Object();
        public void test(){

            synchornized(o){
                //...............
            }
        }
    }

    所有執行到Test例項的synchornized(o)的執行緒,只有一個執行緒可以獲取到監視鎖.

    有時我們會這樣:
        public void test(){

            synchornized(this){
                //...............
            }
        }
    那麼所有執行Test例項的執行緒只能有一個執行緒執行.而synchornized(o)和
synchornized(this)的範圍是不同的,因為執行到Test例項的synchornized(o)的執行緒等待時,其它執行緒可以執行
Test例項的synchornized(o1)部分,但多個執行緒同時只有一個可以執行Test例項的synchornized(this).
    
    而對於    synchornized(Test.class){
                //...............
        }這樣的同步塊而言,所有呼叫Test多個例項的執行緒賜教只能有一個執行緒可以執行.


    

    [synchornized方法]

    如果一個方法宣告為synchornized的,則等同於把在為個方法上呼叫synchornized(this).

    如果一個靜態方法被宣告為synchornized,則等同於把在為個方法上呼叫synchornized(類.class).


    




現在進入wait方法和notify/notifyAll方法.這兩個(或叫三個)方法都是Object物件的方法,而不是
執行緒物件的方法.如同鎖一樣,它們是線上程中呼叫某一物件上執行的.

    class Test{
        public synchornized void test(){
        //獲取條件,int x 要求大於100;
        
            if(x < 100)
                wait();
        }
    }

    這裡為了說明方法沒有加在try{}catch(){}中,如果沒有明確在哪個物件上呼叫wait()方法,則
為this.wait();
    假如:

        Test t = new Test();
    現在有兩個執行緒都執行到t.test();方法.其中執行緒A獲取了t的物件鎖,進入test()方法內.
    這時x小於100,所以執行緒A進入等待.

    當一個執行緒呼叫了wait方法後,這個執行緒就進入了這個物件的休息室(waitset),這是一個虛擬的
物件,但JVM中一定存在這樣的一個資料結構用來記錄當前物件中有哪些程執行緒在等待.
    當一個執行緒進入等待時,它就會釋放鎖,讓其它執行緒來獲取這個鎖.

    所以執行緒B有機會獲得了執行緒A釋放的鎖,進入test()方法,如果這時x還是小於100,執行緒B也進入了
t的休息室.
    這兩個執行緒只能等待其它執行緒呼叫notity[All]來喚醒.

    但是如果呼叫的是有引數的wait(time)方法,則執行緒A,B都會在休息室中等待這個時間後自動喚醒.


    [為什麼真正的應用都是用while(條件)而不用if(條件)]

    在實際的程式設計中我們看到大量的例子都是用        
            while(x < 100)
                wait();go();而不是用if,為什麼呢?
    在多個執行緒同時執行時,if(x <100)是不安全的.因為如果執行緒A和執行緒B都在t的休息室中等待,
這時另一個執行緒使x==100了,並呼叫notifyAll方法,執行緒A繼續執行下面的go().而它執行完成後,x有可能
又小於100,比如下面的程式中呼叫了--x,這時切換到執行緒B,執行緒B沒有繼續判斷,直接執行go();就產生一個
錯誤的條件,只有while才能保證執行緒B又繼續檢查一次.
    

    [notify/notifyAll方法]
    這兩個方法都是把某個物件上休息區內的執行緒喚醒,notify只能喚醒一個,但究竟是哪一個不能確
定,而notifyAll則喚醒這個物件上的休息室中所有的執行緒.



    一般有為了安全性,我們在絕對多數時候應該使用notifiAll(),除非你明確知道只喚醒其中的一個
執行緒.

    那麼是否是隻要呼叫一個物件的wait()方法,當前執行緒就進入了這個物件的休息室呢?事實中,要調
用一個物件的wait()方法,只有當前執行緒獲取了這個物件的鎖,換句話說一定要在這個物件的同步方法或
以這個物件為引數的同步塊中.
    

class MyThread extends Thread{
  Test t = new Test();
    public void run(){
      t.test();
        System.out.println("Thread say:Hello,World!");
    }
}


public class Test {
  
    int x = 0;
    public  void test(){
      if(x==0)
        try{
          wait();
        }catch(Exception e){}
    }
    public static void main(String[] args) throws Exception{
      new MyThread().start();
    }
}

這個執行緒就不會進入t的wait方法而直接列印出Thread say:Hello,World!.
而如果改成:


public class Test {
  
    int x = 0;
    public synchornized void test(){
      if(x==0)
        try{
          wait();
        }catch(Exception e){}
    }
    public static void main(String[] args) throws Exception{
      new MyThread().start();
    }
}
我們就可以看到執行緒一直等待,注意這個執行緒進入等待後沒有其它執行緒喚醒,除非強行退出
JVM環境,否則它一直等待.


所以請記住:
[執行緒要想呼叫一個物件的wait()方法就要先獲得該物件的監視鎖,而一旦呼叫wait()後又立即
釋放該鎖
]
(注:interrupt()非static  interrupted()static  sleep()static   join()非static   yield()static   stop()非static  (static方法就只能本執行緒呼叫,非static可以讓別的執行緒來呼叫)和wait(),notify()/notityAll() 的區別在於,前者為Thread的方法,是和執行緒物件相關的,通過執行緒物件對執行緒起作用。後者為Object的方法,是執行緒在執行某物件的方法時通過物件本身對執行緒其作用,而不是通過執行緒物件直接起作用。注意區別執行緒物件和執行緒)
以上是對執行緒基礎知識的簡單介紹,不進入例項,我們無法真正瞭解它的真實意義.下節我們就
會以例項來進入多執行緒程式設計的 實戰篇