Java 多執行緒學習(執行緒通訊——消費者和生產者)

-Kk發表於2020-11-19

執行緒通訊


  • 假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中的產品取走消費。
  • 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,知道倉庫中的產品被消費者取走為止。
  • 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,知道倉庫中再次放入產品為止。

這是一個執行緒同步問題,生產者和消費者共享一個資源,並且生產者和消費者之間相互依賴,互為條件

在生產者消費者問題中,僅有synchronized是不夠的

  • synchronized 可阻止併發更新同一個共享資源,實現了同步。
  • synchronized 不能用來實現不同執行緒之間的訊息傳遞(通訊)。

Java提供了幾個方法解決執行緒之間的通訊問題:

  • wait() 表示執行緒一直等待,直到其他執行緒通知,與sleep不同,會釋放鎖。
  • wait(long timout) 指定等待的毫秒數
  • notify() 喚醒一個處於等待狀態的執行緒
  • notifyAll() 喚醒同一個物件上所有呼叫wait() 方法的執行緒,優先順序別高的執行緒優先排程。

注意:均是Object類的方法,都只能在同步方法或者同步程式碼塊中使用,否則會丟擲異常IllegalMonitorStateException

1. 解決方式——緩衝區

生產者將生產好的資料放入緩衝區,消費者從緩衝區拿出資料。

在這裡插入圖片描述
程式碼:

package com.mango.gaoji;

// 測試:生產者消費者模型--> 利用緩衝區解決:管程法

// 生產者、消費者、產品、緩衝區
public class TestPC {

    public static void main(String[] args) {
        Syncontanier contanier = new Syncontanier();
        new Productor(contanier).start();
        new Consumer(contanier).start();
    }
}

// 生產者
class Productor extends Thread{
    Syncontanier contanier;

    public Productor(Syncontanier contanier) {
        this.contanier = contanier;
    }

    // 生產

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            contanier.push(new Chicken(i));
            System.out.println("生產了第"+i+"只雞");
        }
    }
}

// 消費者
class Consumer extends Thread{
    Syncontanier contanier;

    public Consumer(Syncontanier contanier) {
        this.contanier = contanier;
    }

    // 消費
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費了第"+contanier.pop().id+"只雞");

        }
    }
}

// 產品
class Chicken{
    int id; // 產品編號

    public Chicken(int id) {
        this.id = id;
    }
}

// 緩衝區
class Syncontanier{

    // 需要一個容器大小
    Chicken[] chickens =new Chicken[10];
    //容器計數器
    int count = 0;
    // 生產者放入產品
    public synchronized void push(Chicken chicken){
        // 如果容器滿了就需要等待消費者消費
        if(count == chickens.length){
            // 通知消費者消費,生產者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果沒有滿,我們就需要丟去產品
        chickens[count] = chicken;
        count++;

        // 可以通知消費者消費了
        this.notifyAll();
    }

    // 消費者消費產品
    public synchronized Chicken pop(){

        // 判斷能否消費
        if(count == 0){
            // 等待生產者生產,消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消費
        count--;
        Chicken chicken = chickens[count];
        //通知生產者生產
        this.notifyAll();
        return chicken;

    }

}

2. 解決方式——訊號燈

標誌位解決。

程式碼:

其中flag作為標誌位

package com.mango.gaoji;

// 測試生產者消費者問題2:訊號燈法,標誌位解決
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();

    }
}

// 生產者——->演員
class Player extends Thread{
    TV tv;

    public Player(TV tv) {
        this.tv = tv;

    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2 == 0){
                this.tv.player("播放中");
            }else{
                this.tv.player("抖音");
            }
        }
    }
}
// 消費者-->觀眾
class Watcher extends Thread{
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}

// 產品--> 節目
class TV {

    // 演員表演,演員等待 T
    // 觀眾觀看,演員等待 F
    String voice; // 表演節目
    boolean flag = true;

    // 表演
    public synchronized void player(String voice){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演員表演了:"+voice);
        // 通知觀眾觀看
        this.notifyAll(); // 通知喚醒
        this.voice = voice;
        this.flag = !this.flag;
    }
    // 觀眾看
    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀眾觀看了:"+voice);
        //通知演員表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

相關文章