java多線程詳解(6)-線程間的通信wait及notify方法


Java多線程間的通信

本文提綱

一. 線程的幾種狀態

二. 線程間的相互作用

三.實例代碼分析

 

一. 線程的幾種狀態

線程有四種狀態,任何一個線程肯定處於這四種狀態中的一種:
(1). 產生(New):線程對象已經產生,但尚未被啟動,所以無法執行。如通過new產生了一個線程對象后沒對它調用start()函數之前。
(2). 可執行(Runnable):每個支持多線程的系統都有一個排程器,排程器會從線程池中選擇一個線程並啟動它。

當一個線程處於可執行狀態時,表示它可能正處於線程池中等待排排程器啟動它;也可能它已正在執行。

如執行了一個線程對象的start()方法后,線程就處於可執行狀態,但顯而易見的是此時線程不一定正在執行中。
(3). 死亡(Dead):當一個線程正常結束,它便處於死亡狀態。如一個線程的run()函數執行完畢后線程就進入死亡狀態。
(4). 停滯(Blocked):當一個線程處於停滯狀態時,系統排程器就會忽略它,不對它進行排程。

二.線程間的相互作用

線程間的相互作用:線程之間需要一些協調通信,來共同完成一件任務。

Object類中相關的方法有兩個notify方法和三個wait方法:

http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

因為wait和notify方法定義在Object類中,因此會被所有的類所繼承。

這些方法都是final的,即它們都是不能被重寫的,不能通過子類覆寫去改變它們的行為。

wait()方法
wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法。

當前的線程必須擁有當前對象的monitor,也即lock,就是鎖。

線程調用wait()方法,釋放它對鎖的擁有權,然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll() 方法),這樣它才能重新獲得鎖的擁有權和恢復執行。

要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。

wait()與sleep()比較

當線程調用了wait()方法時,它會釋放掉對象的鎖。

另一個會導致線程暫停的方法:Thread.sleep(),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放 掉對象的鎖的。


notify()方法
notify()方法會喚醒一個等待當前對象的鎖的線程。

如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖 是由於調用了wait方法中的一個)。

被喚醒的線程是不能被執行的,需要等到當前線程放棄這個對象的鎖。

被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程並沒有什么優先權,也 沒有什么劣勢,

對象的下一個線程還是需要通過一般性的競爭。

notify()方法應該是被擁有對象的鎖的線程所調用。

(This method should only be called by a thread that is the owner of this object's monitor.)

換句話說,和wait()方法一樣,notify方法調用必須放在synchronized方法或synchronized塊中。

 wait()和notify()方法要求在調用時線程已經獲得了對象的鎖,因此對這兩個方法的調用需要放在synchronized方法或 synchronized塊中。

一個線程變為一個對象的鎖的擁有者是通過下列三種方法:

1.執行這個對象的synchronized實例方法。

2.執行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。

3.對於Class類的對象,執行那個類的synchronized、static方法。

三.實例代碼分析

利用兩個線程,對一個整形成員變量進行變化,一個對其增加,一個對其減少,利用線程間的通信,實現該整形變量0101這樣交替的變更。

/** * 進行減少操作的線程。 * @author cary * @version 1.0.0 */
public class DecreaseThread extends Thread { private NumberHolder numberHolder; public DecreaseThread(NumberHolder numberHolder) { this.numberHolder = numberHolder; } @Override public void run() { for (int i = 0; i < 20; ++i) { /** * 進行一定的延時 */
            try { Thread.sleep((long) Math.random() * 1000); } catch (InterruptedException e) { e.printStackTrace(); } /** * 進行減少操作 */ numberHolder.decrease(); } } }

/**
* 進行增加操作的線程。
*
* @author cary
* @version 1.0.0
*/
public class IncreaseThread extends Thread {
private NumberHolder numberHolder;

public IncreaseThread(NumberHolder numberHolder) {
    this.numberHolder = numberHolder;
}

@Override
public void run() {
  for (int i = 0; i < 20; ++i) {
  /**
  * 進行一定的延時
  */
  try {
    Thread.sleep((long) Math.random() * 1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
}

  /**
  * 進行增加操作
  */
  numberHolder.increase();
    }
  }

}

/**
* 進行減少操作的線程。
*
* @author cary
* @version 1.0.0
*/
public class DecreaseThread extends Thread {
private NumberHolder numberHolder;

public DecreaseThread(NumberHolder numberHolder) {
    this.numberHolder = numberHolder;
}

@Override
public void run() {
  for (int i = 0; i < 20; ++i) {
  /**
  * 進行一定的延時
  */
  try {
      Thread.sleep((long) Math.random() * 1000);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }

  /**
  * 進行減少操作
  */
  numberHolder.decrease();
    }
  }

}

 

/**
* 線程測試。
* @author cary
* @version 1.0.0
*/
public class NumberTest {
    public static void main(String[] args) {
      NumberHolder numberHolder = new NumberHolder();
      Thread t1 = new IncreaseThread(numberHolder);
      Thread t2 = new DecreaseThread(numberHolder);
      t1.start();
      t2.start();
    }
}

如果再增加2個線程,即把其中的NumberTest類改為如下:

public class NumberTest { public static void main(String[] args) { NumberHolder numberHolder = new NumberHolder(); Thread t1 = new IncreaseThread(numberHolder); Thread t2 = new DecreaseThread(numberHolder); Thread t3 = new IncreaseThread(numberHolder); Thread t4 = new DecreaseThread(numberHolder); t1.start(); t2.start(); t3.start(); t4.start(); } } 

運行后發現,加上t3和t4之后結果就錯了。

為什么兩個線程的時候執行結果正確而四個線程的時候就不對了呢?

因為線程在wait()的時候,接收到其他線程的通知,即往下執行,不再進行判斷。兩個線程的情況下,喚醒的肯定是另一個線程;

但是在多個線程的情況下,執行結果就會混亂無序。

比如,一個可能的情況是,一個增加線程執行的時候,其他三個線程都在wait,這時候第一個線程調用了notify()方法,

其他線程都將被喚醒,然后執行各自的增加或減少方法。

解決的方法就是:在被喚醒之后仍然進行條件判斷,去檢查要改的數字是否滿足條件,如果不滿足條件就繼續睡眠。

把兩個方法中的if改為while即可。

public class NumberHolder { private int number; public synchronized void increase() { while (0 != number) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 能執行到這里說明已經被喚醒,並且number不為0
        number++; System.out.println(number); // 通知在等待的線程
 notify(); } public synchronized void decrease() { while (0 == number) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 能執行到這里說明已經被喚醒,並且number不為0
        number--; System.out.println(number); notify(); } }



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM