Java多執行緒生產者消費者說明等待喚醒機制問題和虛假喚醒問題
阿新 • • 發佈:2019-02-08
不用等待喚醒機制實現的生產者與消費者
程式碼
package com.hust.juc;
/*
* 生產者和消費者案例
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生產者 A" ).start();
new Thread(cus, "消費者 B").start();
/*
* new Thread(pro, "生產者 C").start(); new Thread(cus, "消費者 D").start();
*/
}
}
// 店員
class Clerk {
private int product = 0;
// 進貨
public synchronized void get() {// 迴圈次數:0
if (product >= 1) {
System.out.println("產品已滿!" );
/*try {
this.wait();
} catch (InterruptedException e) {
}*/
} else {
System.out.println(Thread.currentThread().getName() + " : "
+ ++product);
//this.notifyAll();
}
}
// 賣貨
public synchronized void sale() {// product = 0; 迴圈次數:0
if (product <= 0) {
System.out.println("缺貨!");
/*try {
this.wait();
} catch (InterruptedException e) {
}*/
} else {
System.out.println(Thread.currentThread().getName() + " : "
+ --product);
//this.notifyAll();
}
}
}
// 生產者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
}
// 消費者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
執行結果
只要執行緒得到了CPU就會執行,缺貨也會一直去缺貨,很不科學,所以這個時候我們得用等待喚醒機制。
使用等待喚醒機制
程式碼
就是把上面的程式碼中await和notify的註釋開啟
執行結果
這個時候會發現都執行完了,但是程式並沒有結束,這就是等待喚醒機制遺留問題。
解決等待喚醒機制的問題
我們分析一下,很容易想到是因為,await被喚醒之後是從await的地方繼續執行,那麼直接從else出走掉了,假如那是消費者的最後一輪迴圈,那麼那個最後一輪迴圈的執行緒就結束了,生產者還有一輪或者多倫迴圈沒有結束,生產者執行緒生產出的商品就沒有執行緒去消費就會一直等待。
程式碼去掉else就解決了這個問題
package com.hust.juc;
/*
* 生產者和消費者案例
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生產者 A").start();
new Thread(cus, "消費者 B").start();
/*
* new Thread(pro, "生產者 C").start(); new Thread(cus, "消費者 D").start();
*/
}
}
// 店員
class Clerk {
private int product = 0;
// 進貨
public synchronized void get() {// 迴圈次數:0
if (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out
.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
// 賣貨
public synchronized void sale() {// product = 0; 迴圈次數:0
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out
.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
// 生產者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
}
// 消費者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
多執行緒下的虛假喚醒
說白了其實這還是“單執行緒“,因為只有一個執行緒生產一個執行緒消費,如果現在有兩個執行緒去生產,兩個執行緒去消費,那麼就會出現虛假喚醒的情況。
虛假喚醒程式碼
把上面的程式碼中CD執行緒註釋掉的開啟就行。
執行截圖
虛假喚醒問題分析
仔細考慮一下,假如現在消費者執行緒C執行,此時沒有商品,消費者執行緒C等待,這個時候消費者執行緒D也拿到了CPU,也是沒有商品的狀態,也等待。這個時候一個生產者生產出了一個商品,此時喚醒了兩個等待的消費者,往下執行,那麼很自然就產生了-1等負數的情況。
解決辦法就是把wait放在迴圈裡
package com.hust.juc;
/*
* 生產者和消費者案例
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生產者 A").start();
new Thread(cus, "消費者 B").start();
new Thread(pro, "生產者 C").start();
new Thread(cus, "消費者 D").start();
}
}
// 店員
class Clerk {
private int product = 0;
// 進貨
public synchronized void get() {// 迴圈次數:0
while (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out
.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
// 賣貨
public synchronized void sale() {// product = 0; 迴圈次數:0
while (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out
.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
// 生產者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
}
// 消費者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
執行截圖
這個才會得到解決。