Java線程通訊方法之wait()、nofity() 詳解 本文將探討以下問題: 1. synchronized 代碼塊使用 2. notify()與notifyAll()的區別 3. Java wait(),notify()如何使用 參考文章: "Java並行(2): Monitor" "Java ...
Java線程通訊方法之wait()、nofity() 詳解
本文將探討以下問題:
- synchronized 代碼塊使用
- notify()與notifyAll()的區別
- Java wait(),notify()如何使用
參考文章:
Java並行(2): Monitor
Java併發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
Java的wait(), notify()和notifyAll()使用心得
原創文章,歡迎轉載!
synchronized 代碼塊使用
我們知道當多個線程同時訪問一個線程安全方法時我們可以使用synchronized關鍵字來給方法加鎖。當某個線程需
要訪問方法時,會先獲取訪問該方法的鎖,當訪問完畢再釋放鎖。當多個線程在等待調用隊列中,操作系統根據一
定的調度演算法,取出下一個線程來執行方法,完成方法的並行到串列的執行過程。每個對象都擁有一個Monitor,我
們可以將Monitor理解為對象的鎖。每個線程訪問任意對象時必須要先獲取該對象的Monitor 才能訪問。當synchro-
nized修飾一個對象時,它控制多線程訪問該對象的方法正是通過對象的Monitor實現。請看下麵計數代碼:
public class SynchronizedImpl implements Runnable{
public static MyInteger num = new MyInteger() ;
public void run() {
// 鎖定 num 引用的對象
synchronized (num){
// 對num 的成員變數value自增,步進為1
num.setValue(num.getValue()+1);
System.out.println(Thread.currentThread().getName()+":"+SynchronizedImpl.num.getValue());
}
}
public static void main(String[] args) {
for(int i =0 ; i < 1000 ;i++){
Thread t = new Thread(new SynchronizedImpl());
t.start();
}
}
}
class MyInteger{
int value =0 ;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
上述代碼 num
所引用的對象(ps: 事實上num
並不是一個對象,只是棧中的一個引用,記錄的是它所引用的對象的地址,下
文為了敘述方便把num
稱為對象) ,同時被多個線程訪問。(ps:事實上不會是1000個線程同時訪問,同時訪問一個對象的
線程數小於等於cpu的核心數)。
從列印的結果來看,雖然線程並不是順序執行,但是保證每個線程都訪問一次num
對象,並且對num
對象的 value
屬性 +1 。
notify() 和notifyAll()
顧名思義notify()是喚醒一個正在等待的線程,notifyAll()是喚醒所有正在等待的線程。這樣的解釋 並不會讓我們很好理解
二者之間的區別。
notify()
notify是通知操作系統喚醒一個正在等待的獲取對象鎖的線程,當有多個等待線程時候, 操作系統會根據一定的調度演算法調
度一個線程,當正在占有對象鎖的線程釋放鎖的時候操作系統調度的這個線程就會執行。而而剩下的那些沒有被notify的線程
就不會獲取執行機會。
notifyAll()
當有多個等待線程時,所有的等待線程都會被喚醒,他們會根據操作系統的調度順序依次執行。下麵的代碼說明二者的區別:
public class NofityTest implements Runnable {
private Object lock ;
private int num ;
public NofityTest(Object lock, int i) {
this.lock = lock ;
num = i ;
}
@Override
public void run() {
synchronized(lock){
try {
lock.wait();
System.out.println("--- "+num);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 利用obj 的wait 方法實
final Object lock = new Object() ;
new Thread(new NofityTest(lock,1)).start();
new Thread(new NofityTest(lock,2)).start();
try {
// 等待另外兩個線程進入wait狀態
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
// 請將 lock.notify() 改成lock.notifyAll() 再執行觀察二者區別 !!
lock.notify();
}
}
}
wait()和notify()
對象的wait()是讓當前線程釋放該對象Monitor鎖並且進入訪問該對象的等待隊列,當前線程會進入掛起狀態,等待操作系統喚起(notify)
掛起的線程重新獲取對該對象的訪問鎖才能進入運行狀態。因為自身已經掛起,所以已經掛起的線程無法喚醒自己,必須通過別的線程
告訴操作系統,再由操作系統喚醒。Monitor是不能被併發訪問的(否則Monitor狀態會出錯,操作系統根據錯誤的狀態調度導致系統錯亂),
而wait和nofity 正是改變Monitor的狀態(請參考 PV操作) 所以使用wait、notify方法時,必須對對象使用synchronized加鎖,只有線程獲
取對象的Monitor鎖之後才能進行wait、notify操作否則將拋出IllegalMonitorStateException異常。我們來看一段代碼:
public class PrintAB implements Runnable{
private Object lock ;
private char ch ;
public PrintAB(Object lock ,char ch){
this.lock = lock ;
this.ch = ch ;
}
@Override
public void run() {
while(true){
synchronized (lock){
try {
/**
* 第一次執行並不會喚醒任何線程,
* 第二次以及以後就會喚醒另外一個線程獲取Monitor鎖,因為只有一個線程掛起
* 而notify() 就是喚醒一個線程
*/
lock.notify();
System.out.println(Thread.currentThread().getName()+":"+ch);
/**
* synchronized 代碼塊執行完畢之後才會交出Monitor鎖,別的線程才有執行機會
* wait 執行過之後當前線程就掛起了,然後釋放鎖,接著已經被操作系統notify
* 的線程獲取Monitor 開始執行
*/
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 我們最好將 lock 聲明為final ,防止重新賦值後導致synchronized鎖定對象發生改變。
final Object lock = new Object();
new Thread(new PrintAB(lock,'A')).start();
new Thread(new PrintAB(lock,'B')).start();
}
}
上述代碼實現了交替列印 字元A
和字元B
。當一個線程列印完畢之後自己就會掛起,必須等待另外一個線程列印並將
之喚醒,就實現了交替列印的效果。