在上一篇當中,我們提及了Java語言Object類的九大方法,并重點講解了其中的getClass(),finalize(),toString(),equals(),hashcode(),
沒看過的小伙伴,可以點擊閱讀上一篇:漫畫:Object類很大,你忍一下
這一次,我們來重點講解 wait(),notify(),notifyAll() 這三大方法,





// 執行這個方法后,持有此物件監視器的執行緒會進入等待佇列,同時釋放鎖
// 如果不在synchronized修飾的方法或代碼塊里呼叫,則會拋出IllegalMonitorStateException 例外
// 如果當前執行緒在等待時被中斷,則拋出InterruptedException例外
public final void wait() throws InterruptedException {
wait(0);
}
// timeout是執行緒等待時間,時間結束則自動喚醒,單位ms
// Java默認的實作方式,native實作
public final native void wait(long timeout) throws InterruptedException;
// nanos是更精確的執行緒等待時間,單位ns(1 ms = 1,000,000 ns)
// Java默認的實作方式
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}



含引數的wait()方法呼叫以后,執行緒可以在等待時間結束后被喚醒;無參的wait()方法呼叫后,則必須等待持有該物件監視器的執行緒主動呼叫notify()或notifyAll()方法后才能被喚醒,
兩者之間的區別,在于notify()方法喚醒在此物件監視器上等待的單個執行緒,而notifyAll()方法則喚醒在此物件監視器上等待的所有執行緒,
與wait方法一樣,執行notify方法的執行緒也必須提前獲取鎖,需要注意的是,被notify方法喚醒的執行緒并不會立即執行,因為要等呼叫notify方法的執行緒釋放鎖之后才會獲取到鎖,


有的Java虛擬機(VM)會選擇最先呼叫wait方法的執行緒,也有的則會隨機選擇一個執行緒,盡管notify方法的處理速度比notifyAll方法更快,但使用notifyAll方法更為穩妥,
notify與notifyAll的方法宣告如下:
public final native void notify();
public final native void notifyAll();





有一家公司開始想要招聘程式員,于是面試了幾名候選人:

但公司錄用程式員的時間是不確定的,需要綜合考慮,無法給出及時反饋,于是告訴他們回去等通知,

經過事后的比較和研究,面試官覺得大黃是招聘的最佳人選,于是讓HR小姐姐通知大黃,也就是“喚醒”了大黃:



(1)wait()、notify()和notifyAll()必須在synchronized修飾的方法或代碼塊中使用,
(2)在while回圈里而不是if陳述句下使用wait(),確保在執行緒睡眠前后都檢查wait()觸發的條件(防止虛假喚醒),
(3)wait()方法必須在多執行緒共享的物件上呼叫,


生產者/消費者模型能解決絕大多數并發問題,通過平衡生產執行緒和消費執行緒的作業能力,來提高程式的整體處理資料的速度,
生產者執行緒和消費者執行緒的處理速度差異,會引起消費者想要獲取資料時,資料還沒生成或者生產者想要交付資料,卻沒有消費者接收的問題,對于這樣的問題,生產者/消費者模型可以消除這個差異,


首先,按照注意事項(1)和(2)的要求,定義一個生產者,往佇列里添加元素:
// 生產者,有詳細的注釋
public class Producer implements Runnable{
private Queue<Integer> queue;
private int maxSize;
public Producer(Queue<Integer> queue, int maxSize){
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
// 這里為了方便演示做了一個死回圈,現實開發中不要這樣搞
while (true){
//(1)wait()、notify()和notifyAll()必須在synchronized修飾的方法或代碼塊中使用
synchronized (queue){
//(2)在while回圈里而不是if陳述句下使用wait(),確保在執行緒睡眠前后都檢查wait()觸發的條件(防止虛假喚醒)
while (queue.size() == maxSize){
try{
System.out.println("Queue is Full");
// 生產者執行緒進入等待狀態,在此物件監視器上等待的所有執行緒(其實只有那個消費者執行緒)開始爭奪鎖
queue.wait();
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
Random random = new Random();
int i = random.nextInt();
System.out.println("Produce " + i);
queue.add(i);
// 喚醒這個Queue物件的等待池中的所有執行緒(其實只有那個消費者執行緒),等待獲取物件監視器
queue.notifyAll();
}
}
}
}
接下來,再定義一個與之類似的消費者類,除了從佇列里移除元素的邏輯之外,整體代碼大同小異:
// 消費者類
public class Consumer implements Runnable{
private Queue<Integer> queue;
private int maxSize;
public Consumer(Queue<Integer> queue, int maxSize){
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true){
synchronized (queue){
while (queue.isEmpty()){
System.out.println("Queue is Empty");
try{
queue.wait();
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
int v = queue.remove();
System.out.println("Consume " + v);
queue.notifyAll();
}
}
}
}
最后撰寫符合注意事項(3)的測驗代碼:
public void test(){
//(3)wait()方法必須在多執行緒共享的物件上呼叫
// 這個佇列就是給消費者、生產者兩個執行緒共享的物件
Queue<Integer> queue = new LinkedList<>();
int maxSize = 5;
Producer p = new Producer(queue, maxSize);
Consumer c = new Consumer(queue, maxSize);
Thread pT = new Thread(p);
Thread pC = new Thread(c);
// 生產者執行緒啟動,獲取鎖
pT.start();
// 消費者執行緒啟動
pC.start();
}
最終的查看運行結果如下:
Produce 1604006010
Produce 1312202442
Produce -1478853208
Produce 1460408111
Produce 1802825495
Queue is Full
Consume 1604006010
Consume 1312202442
Consume -1478853208
Consume 1460408111
Consume 1802825495
Queue is Empty





轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/291914.html
標籤:其他
