一涮拗、前言
先來貼一段生產者媒抠,消費者模型的代碼:
public class Wait {
public volatile static int count = 0;
public static void main(String[] args) {
System.out.println("開啟10個生產者");
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();
}
}
}
// 生產者
class Producer implements Runnable {
@Override
public void run() {
synchronized (Wait.class) {
System.out.println("生產者" + Thread.currentThread().getName() + "準備生產");
while (Wait.count >= 10) {
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("當前count值" + Wait.count);
}
}
}
// 消費者
class Consumer implements Runnable {
@Override
public void run() {
synchronized (Wait.class) {
System.out.println("消費者" + Thread.currentThread().getName() + "準備消費");
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("當前count值" + Wait.count);
}
}
}
二科乎、為什么wait與notify只能在synchronized代碼塊中執(zhí)行
首先我們要知道壁畸,wait 與 notify 方法是屬于 Object 的,每個對象都擁有這兩個方法,這與線程特有的方法不同捏萍。
synchronized 代碼塊使用的是對象內置的監(jiān)視器鎖太抓,每個對象都擁有自己的監(jiān)視器鎖,而 wait令杈,notify 方法實際上也是在鎖對象上執(zhí)行的走敌。只有在 synchronized 代碼塊中才使用了鎖對象,所以調用 wait逗噩,notify 才有意義掉丽。
了解一下前置知識:
JVM 會為每個鎖對象維護兩個集合,Entry Set 與 Wait Set异雁,如果一個線程 A捶障,持有了該鎖對象,那么對于其余的線程想要獲取該鎖對象的話纲刀,它只能進入 Entry Set项炼,并且此時線程處于 BLOCKED 狀態(tài);如果一個線程 A 調用了鎖對象的 wait 方法示绊,那么線程 A 會釋放鎖對象锭部,之后進入鎖對象的 Wait Set 中,并且處于 WAITING 狀態(tài)面褐。
當前線程調用鎖對象的 wait 方法空免,首先會釋放鎖,然后進入鎖對象的 Wait Set 中盆耽;當前線程調用鎖對象的 notify 方法蹋砚,會從鎖對象的 Wait Set 中喚醒一個線程。
三摄杂、為什么生產者消費者要用 while 循環(huán)做判斷
以消費者為例坝咐,在調用鎖對象的 wait 方法時,使用的是 while 循環(huán)析恢,如果將 while 循環(huán)換成 if 條件會產生什么問題墨坚?
考慮一種情況,消費者 A 在 wait 方法停住映挂,消費者 B 消費了最后一個資源泽篮,調用 notify 喚醒了消費者 A,消費者 A 直接從 wait 方法往下走柑船,此時資源為空帽撑,就會導致出現問題。如果使用 while 循環(huán)鞍时,那么消費者 A 被喚醒后亏拉,還是會判斷一次資源情況扣蜻,就不會出現問題。
四及塘、為什么要用 notifyAll 不用 notify
還是以消費者為例莽使,如果用的是 notify,一個消費者消費了最后一個資源笙僚,然后又喚醒了一個消費者芳肌,被喚醒的消費者以為沒有資源而進入等待狀態(tài),此時所有的線程都在 wait 肋层,也就是造成了死鎖問題亿笤。
五、能不能用 Lock 實現生產者消費者
notifyAll 會造成性能問題槽驶,可以用 Lock 鎖對象實現生產者消費者:
代碼實例:
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() + "生產");
i ++;
System.out.println(Thread.currentThread().getName() + "通知其他人");
consumerSet.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
},"生產者").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)建多個等待集责嚷,所以可以指定喚醒哪個等待集中的線程,不會出現喚醒相同類型線程導致的問題掂铐。
其中罕拂,Lock 對象相當于監(jiān)視器鎖,condition 集相當于 Wait Set全陨,await 方法相當于 wait 方法爆班,signal 方法相當于 notify 方法。
六辱姨、總結
- wait notify 是屬于鎖對象的方法柿菩,操作的是鎖對象維護的集合,集合中存放的是線程雨涛;
- 永遠要在 while 循環(huán)中調用 wait 方法枢舶;
- 使用 Lock 對象能獲得更好的操作;