問題描述:假設倉庫中隻能存放一件産品,生産者将生産的産品放入倉庫,然後消費者取出來使用。
分析:這是一個線程同步問題,生産者和消費者共享同一個資源,并且生産者和消費者之間互相依賴、互為條件。對于生産者,沒有生産産品之前,要通知消費者等待。在生産了一個産品之後,停止生産,通知消費者來消費。對于消費者,在消費一個産品之後,通停止消費。并且要通知生産者去生産。
因為這裡面涉及到生産者線程和消費者線程之間的通信,是以隻有 synchronized 是不夠的。
Java中提供了5個方法來解決線程之間的通信,其中常用的是wait() 和 notify()方法。
final void wait() 表示線程一直等待,直到其他線程通知;
void wait(long timeout) 線程等待指定毫秒級别的參數的時間
final void wait(long timeout, int nanos) 線程等待指定毫秒+微妙的時間
final void notify() 喚醒一個處于等待狀态的線程
final void notifyAll() 喚醒同一個對象上所有調用wait()方法的線程,優先級别高的線程優先運作。
注意:它們均是Object類的方法,都隻能在同步方法或者同步代碼塊中使用,否則會抛出異常。
下面是源碼部分的書寫:
//我們定義一個産品類,并且其中提供了Product類的name和color屬性的set和get方法。
//還有一個isProduct屬性來代表目前倉庫中是否有産品,其實就是一個互斥鎖。
//同時,我們不再在生産者線程和消費者線程中寫 生産産品的put()方法 和 消費産品的get()方法的詳細内容,
//而是直接在Product類中提供,然後在生産者和消費者線程中去調用。
//最後,我們先将put() get()方法用 synchronized關鍵字 聲明為 同步方法 。
//然後用互斥鎖 isProduct ,并結合wait() notify()方法,實作互斥操作。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:45
* @description: 産品類
*/
public class Product {
private String name; //名字 :玉米 饅頭
private String color; //顔色 :黃色 白色
private boolean isProduct = false; //預設沒有生産
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//生産商品
public synchronized void put(String name, String color) throws InterruptedException {
//1.如果有商品了,等待喚醒
if(isProduct)
wait();
//2.生産商品
this.setName(name);
//加入sleep之後,進一步觀察是否出現 生産玉米白色 的錯誤現象。
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setColor(color);
System.out.println("生産者生産商品,并且放入倉庫中." + name + "---" + color);
//3.修改isProduct的值
isProduct = true;
//4.喚醒消費者
notify();
}
//消費商品
public synchronized void get() throws InterruptedException {
//1.等待商品
if(!isProduct)
wait();
//2.消費商品
System.out.println("消費者從倉庫取出商品." + this.getName() + "---" + this.getColor());
//3.修改isProduct的值
isProduct = false;
//4.喚醒生産者
notify();
}
}//class end
//這裡是生産者線程,直接調用Product類中的put()方法。采用實作Runnable接口的方式來定義。
//其中,提供了兩個構造函數,并且在run()方法的重寫中,加入對i的判斷,當i為偶數時,生産白色的饅頭;i為奇數時,生産黃色的玉米。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:46
* @description: 生産者類
*/
public class Producer implements Runnable{
private Product product;
public Producer(){
super();
}
public Producer(Product product){
super();
this.product = product;
}
//饅頭(i%2 = 0)和玉米(i%2 = 1)交替生産
@Override
public void run() {
int i = 0;
while(true){
//進行生産
if(i%2 == 0){
try {
product.put("饅頭","白色");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
try {
product.put("玉米","黃色");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//改變i的值
i++;
}
}
}//class end
//這裡是消費者線程,直接調用Product類中的get()方法。采用實作Runnable接口的方式來定義。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:46
* @description: 消費者線程
*/
public class Consumer implements Runnable {
private Product product;
public Consumer(){
super();
}
public Consumer(Product product) {
super();
this.product = product;
}
@Override
public void run() {
while(true){
try {
product.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}//class end
//這是我們的測試類
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:42
* @description:
* 業務:
* 1、倉庫中隻放一件商品
* 2、生産者交替生産玉米和饅頭
* 3、生産者和消費者交替工作
* 設計:
* 1、産品類 Product(name、color)
* 2、生産者線程 Producer
* 3、消費者線程 Consumer
* 4、測試類 Test
*
* 問題1:兩個線程之間沒有互動
* 問題2:和商品之間沒有關系 解決:提供商品類、生産者交替生産饅頭和玉米、消費者消費、測試類中給生産者和消費者傳入同一個産品
* 問題3:出現白色玉米和黃色饅頭的錯誤情況 解決:同步代碼塊
* 問題4:生産放到生産者線程中,消費放到消費者線程中,不符合面向對象的封裝思想 解決:把生産和消費作為方法放到product類中
* 問題5:交替生産玉米和饅頭 其實就是問題1,但最後才去解決:線程通信 借助方法:wait() notify() 注意隻要方法聲明中有synchronized 就可以直接調用這些方法
*
*/
public class Test {
public static void main(String[] args) {
//建立兩個線程
//建立産品
Product p = new Product();
Thread th1 = new Thread(new Producer(p));
Thread th2 = new Thread(new Consumer(p));
//啟動線程
th1.start();
th2.start();
}
}//class end
測試結果如下:
