java多線程編程 – 基礎篇(四)[wait(),notify()/notityAll()方法]

來源:互聯網
上載者:User
java多線程編程 - 基礎篇(四) [wait(),notify()/notityAll()方法]   來源於:轉載自dev2dev網友axman的go deep into java專欄。

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

  [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中,用synchronized關鍵字來實現互斥控制(暫時這樣認為,JDK1.5已經發展了新的機制)
[synchronized關鍵字]

  把一個單元聲明為synchronized,就可以讓在同一時間只有一個線程操作該方法.

  有人說synchronized就是一把鎖,事實上它確實存在鎖,但是是誰的鎖,鎖誰,這是一個非常複雜的問題.

  每個對象只有一把監視鎖(monitor lock),一次只能被一個線程擷取.當一個線程擷取了這一個鎖後,其它線程就只能等待這個線程釋放鎖才能再擷取.

那麼synchronized關鍵字到底鎖什麼?得到了誰的鎖?

對於同步塊,synchronized擷取的是參數中的對象鎖:

  1. synchronized(obj){
  2.   //...............
  3. }
  線程執行到這裡時,首先要擷取obj這個執行個體的鎖,如果沒有擷取到線程只能等待.如果多個線程執行到這裡,只能有一個線程擷取obj的鎖,然後執行{}中的語句,所以,obj對象的作用範圍不同,控製程序不同.

  假如:

  1. public void test(){
  2.   Object o = new Object();
  3.   synchronized(obj){
  4.    //...............
  5.    }
  6. }
  這段程式控制不了任何,多個線程之間執行到Object o = new Object();時會各自產生一個對象然後擷取這個對象有監視鎖,各自皆大歡喜地執行.

  而如果是類的屬性:

  1. class Test {
  2.   Object o = new Object();
  3.   public void test() {
  4.     synchronized (o) {
  5.       // ...............
  6.     }
  7.   }
  8. }
  所有執行到Test執行個體的synchronized(o)的線程,只有一個線程可以擷取到監視鎖.

  有時我們會這樣:

  1.   public void test() {
  2.     synchronized (this) {
  3.       // ...............
  4.     }
  5.   }
  那麼所有執行Test執行個體的線程只能有一個線程執行.而synchronized(o)和synchronized(this)的範圍是不同的,因為執行到Test執行個體的synchronized(o)的線程等待時,其它線程可以執行Test執行個體的synchronized(o1)部分,但多個線程同時只有一個可以執行Test執行個體的synchronized(this).]

  而對於

  1. synchronized(Test.class){
  2.     //...............
  3. }
  這樣的同步塊而言,所有調用Test多個執行個體的線程賜教只能有一個線程可以執行.
[synchronized方法]

  如果一個方法聲明為synchronized的,則等同於把在為個方法上調用synchronized(this).

  如果一個靜態方法被聲明為synchronized,則等同於把在為個方法上調用synchronized(類.class).

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

 
  1. class Test {
  2.   int x;
  3.   public synchronized void test() throws InterruptedException {
  4.     // 擷取條件,int x 要求大於100;
  5.     if (x < 100)
  6.       wait();
  7.   }
  8. }
  這裡為了說明方法沒有加在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()方法,只有當前線程擷取了這個對象的鎖,換句話說一定要在這個對象的同步方法或以這個對象為參數的同步塊中.

  1. class MyThread extends Thread {
  2.   Test t = new Test();
  3.   public void run() {
  4.     t.test();
  5.     System.out.println("Thread say:Hello,World!");
  6.   }
  7. }
  8. public class Test {
  9.   int x = 0;
  10.   public void test() {
  11.     if (x == 0)
  12.       try {
  13.         wait();
  14.       } catch (Exception e) {
  15.       }
  16.   }
  17.   public static void main(String[] args) throws Exception {
  18.     new MyThread().start();
  19.   }
  20. }
  這個線程就不會進入t的wait方法而直接列印出Thread say:Hello,World!.

  而如果改成:

  1. public class Test {
  2.   int x = 0;
  3.   public synchronized void test() {
  4.     if (x == 0)
  5.       try {
  6.         wait();
  7.       } catch (Exception e) {
  8.       }
  9.   }
  10.   public static void main(String[] args) throws Exception {
  11.     new MyThread().start();
  12.   }
  13. }
  我們就可以看到線程一直等待,注意這個線程進入等待後沒有其它線程喚醒,除非強行退出JVM環境,否則它一直等待.

  所以請記住:

  [線程要想調用一個對象的wait()方法就要先獲得該對象的監視鎖,而一旦調用wait()後又立即釋放該鎖]

  以上是對線程基礎知識的簡單介紹,不進入執行個體,我們無法真正瞭解它的真實意義.下節我們就會以執行個體來進入多線程編程的 實戰篇

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.