一、前言
先來貼一段生產(chǎn)者,消費者模型的代碼:
public class Wait {
public volatile static int count = 0;
public static void main(String[] args) {
System.out.println("開啟10個生產(chǎn)者");
for (int i = 0; i < 100; i++) {
new Thread(new Producer(), "線程:" + i).start();
}
System.out.println("開啟10個消費者");
for (int i = 0; i < 100; i++) {
new Thread(new Consumer(), "線程:" + i).start();
}
}
}
// 生產(chǎn)者
class Producer implements Runnable {
@Override
public void run() {
synchronized (Wait.class) {
System.out.println("生產(chǎn)者" + Thread.currentThread().getName() + "準(zhǔn)備生產(chǎn)");
while (Wait.count >= 10) {
System.out.println("隊列已滿,生產(chǎn)者" + Thread.currentThread().getName() + "停止生產(chǎn)");
try {
Wait.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生產(chǎn)者" + Thread.currentThread().getName() + "生產(chǎn)完畢");
Wait.count++;
System.out.println("生產(chǎn)者" + Thread.currentThread().getName() + "喚醒其他線程");
Wait.class.notifyAll();
System.out.println("當(dāng)前count值" + Wait.count);
}
}
}
// 消費者
class Consumer implements Runnable {
@Override
public void run() {
synchronized (Wait.class) {
System.out.println("消費者" + Thread.currentThread().getName() + "準(zhǔn)備消費");
while (Wait.count <= 0) {
System.out.println("隊列已空,消費者" + Thread.currentThread().getName() + "停止消費");
try {
Wait.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消費者" + Thread.currentThread().getName() + "消費完畢");
Wait.count--;
System.out.println("消費者" + Thread.currentThread().getName() + "喚醒其他線程");
Wait.class.notifyAll();
System.out.println("當(dāng)前count值" + Wait.count);
}
}
}
二、為什么wait與notify只能在synchronized代碼塊中執(zhí)行
首先我們要知道,wait 與 notify 方法是屬于 Object 的,每個對象都擁有這兩個方法,這與線程特有的方法不同。
synchronized 代碼塊使用的是對象內(nèi)置的監(jiān)視器鎖,每個對象都擁有自己的監(jiān)視器鎖,而 wait,notify 方法實際上也是在鎖對象上執(zhí)行的。只有在 synchronized 代碼塊中才使用了鎖對象,所以調(diào)用 wait,notify 才有意義。
了解一下前置知識:
JVM 會為每個鎖對象維護兩個集合,Entry Set 與 Wait Set,如果一個線程 A,持有了該鎖對象,那么對于其余的線程想要獲取該鎖對象的話,它只能進入 Entry Set,并且此時線程處于 BLOCKED 狀態(tài);如果一個線程 A 調(diào)用了鎖對象的 wait 方法,那么線程 A 會釋放鎖對象,之后進入鎖對象的 Wait Set 中,并且處于 WAITING 狀態(tài)。
當(dāng)前線程調(diào)用鎖對象的 wait 方法,首先會釋放鎖,然后進入鎖對象的 Wait Set 中;當(dāng)前線程調(diào)用鎖對象的 notify 方法,會從鎖對象的 Wait Set 中喚醒一個線程。
三、為什么生產(chǎn)者消費者要用 while 循環(huán)做判斷
以消費者為例,在調(diào)用鎖對象的 wait 方法時,使用的是 while 循環(huán),如果將 while 循環(huán)換成 if 條件會產(chǎn)生什么問題?
考慮一種情況,消費者 A 在 wait 方法停住,消費者 B 消費了最后一個資源,調(diào)用 notify 喚醒了消費者 A,消費者 A 直接從 wait 方法往下走,此時資源為空,就會導(dǎo)致出現(xiàn)問題。如果使用 while 循環(huán),那么消費者 A 被喚醒后,還是會判斷一次資源情況,就不會出現(xiàn)問題。
四、為什么要用 notifyAll 不用 notify
還是以消費者為例,如果用的是 notify,一個消費者消費了最后一個資源,然后又喚醒了一個消費者,被喚醒的消費者以為沒有資源而進入等待狀態(tài),此時所有的線程都在 wait ,也就是造成了死鎖問題。
五、能不能用 Lock 實現(xiàn)生產(chǎn)者消費者
notifyAll 會造成性能問題,可以用 Lock 鎖對象實現(xiàn)生產(chǎn)者消費者:
代碼實例:
public class PAndC {
private volatile static int i = 0;
private static Lock lock = new ReentrantLock();
private static Condition producerSet = lock.newCondition();
private static Condition consumerSet = lock.newCondition();
public static void main(String[] args) {
for (int j = 0; j < 5; j++) {
new Thread(() -> {
while(true){
lock.lock();
try {
if(i >= 10){
System.out.println(Thread.currentThread().getName() + "等待");
producerSet.await();
}
System.out.println(Thread.currentThread().getName() + "生產(chǎn)");
i ++;
System.out.println(Thread.currentThread().getName() + "通知其他人");
consumerSet.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
},"生產(chǎn)者").start();
}
for (int j = 0; j < 10; j++) {
new Thread(() -> {
while(true){
lock.lock();
try {
if(i <= 0){
System.out.println(Thread.currentThread().getName() + "等待");
consumerSet.await();
}
System.out.println(Thread.currentThread().getName() + "消費");
i --;
System.out.println(Thread.currentThread().getName() + "通知其他人");
producerSet.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
},"消費者").start();
}
}
}
Lock 對象可以創(chuàng)建多個等待集,所以可以指定喚醒哪個等待集中的線程,不會出現(xiàn)喚醒相同類型線程導(dǎo)致的問題。
其中,Lock 對象相當(dāng)于監(jiān)視器鎖,condition 集相當(dāng)于 Wait Set,await 方法相當(dāng)于 wait 方法,signal 方法相當(dāng)于 notify 方法。
六、總結(jié)
- wait notify 是屬于鎖對象的方法,操作的是鎖對象維護的集合,集合中存放的是線程;
- 永遠(yuǎn)要在 while 循環(huán)中調(diào)用 wait 方法;
- 使用 Lock 對象能獲得更好的操作;