1. 程式人生 > 其它 >Java築基之執行緒併發生產消費者模式

Java築基之執行緒併發生產消費者模式

技術標籤:Java 築基筆記集java多執行緒

執行緒併發協作(生產者/消費者模式)

在這裡插入圖片描述

  • 生產者

負責生產資料的模組(這裡模組可能是:方法、物件、執行緒、程序)。

  • 消費者

負責處理資料的模組(這裡模組可能是:方法、物件、執行緒、程序)。

  • 倉庫

消費者不能直接使用生產者的資料,它們之間有個“倉庫”。生產者將生產好的資料放入“倉庫”,消費者從“倉庫”拿要處理的資料

  • 注意:
  1. 倉庫作為消費者與生產者之間的緩衝區,使生產與消費的執行緒分離大道解耦的效果
  2. 解決生產消費過載,生產資料慢時,緩衝區仍有資料,不影響消費者消費;消費處理資料慢時,生產者仍然可以繼續往緩衝區裡面放置資料

例項需求

  • 《wait/notify實現生產者和消費者程式》
  • 採用多執行緒技術,例如wait/notify,設計實現一個符合生產者和消費者問題的程式,
  • 對某一個物件(槍膛)進行操作,其最大容量是10顆子彈,
  • 生產者執行緒是一個壓入執行緒,它不斷向槍膛中壓入子彈,
  • 消費者執行緒是一個射出執行緒,它不斷從槍膛中射出子彈。
/**
 * @author LHW
 * @date 2020/12/25.
 * Description:子彈實體類
 */
public class Bullet {
    public int id;
}

/**
 * @author LHW
 * @date 2020/12/25.
 * Description:槍膛 壓入發射子彈
 */
public class Clip {
    public int index = 0;//壓入與發射的標誌位
    public Bullet[] bullets = new Bullet[10];//快取子彈彈夾

    //壓入子彈入槍膛
    public synchronized void pushBullet(Bullet bullet) {
        try {
            while (index == bullets.length) {
                System.out.println("pushBullet 彈夾已滿進入等待。。。。。index=" + index);
                this.wait();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notify();//喚醒等待執行緒
        bullets[index] = bullet;
        index++;
    }

    //槍膛射出子彈
    public synchronized Bullet shootBullet() {

        try {
            while (index == 0) {
                System.out.println("shootBullet 槍膛為空進入等待。。。。。index=" + index);
                wait();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notify();
        index--;
        return bullets[index];
    }
}

/**
 * @author LHW
 * @date 2020/12/25.
 * Description:
 * 《wait/notify實現生產者和消費者程式》
 * 採用多執行緒技術,例如wait/notify,設計實現一個符合生產者和消費者問題的程式,
 * 對某一個物件(槍膛)進行操作,其最大容量是10顆子彈,
 * 生產者執行緒是一個壓入執行緒,它不斷向槍膛中壓入子彈,
 * 消費者執行緒是一個射出執行緒,它不斷從槍膛中射出子彈。
 */
public class ShootingMain {
    public static void main(String[] args) {
        Clip mClip = new Clip();
        new PushThread(mClip).start();
        new ShootThread(mClip).start();
    }

    static class PushThread extends Thread {
        Clip mClip;

        PushThread(Clip clip) {
            mClip = clip;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < mClip.bullets.length; i++) {
                Bullet bullet = new Bullet();
                bullet.id = i;
                mClip.pushBullet(bullet);
                System.out.println("PushThread 子彈壓入槍膛。。。" + bullet.id);
                //如果這裡不執行睡眠等待 生產會壓入所有子彈後執行消費執行緒的射出子彈
                SleepTools.ms(1000);//睡眠一秒 阻塞生產執行緒 執行消費執行緒
            }
        }
    }

    static class ShootThread extends Thread {
        Clip mClip;

        ShootThread(Clip clip) {
            mClip = clip;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < mClip.bullets.length; i++) {
                Bullet bullet = mClip.shootBullet();
                System.out.println("ShootThread 子彈射出槍膛。。。" + bullet.id);
                SleepTools.ms(1000);//睡眠一秒 阻塞消費執行緒 執行生產執行緒

            }
        }
    }
}

結果輸出


PushThread 子彈壓入槍膛。。。0
ShootThread 子彈射出槍膛。。。0
shootBullet 槍膛為空進入等待。。。。。index=0
PushThread 子彈壓入槍膛。。。1
ShootThread 子彈射出槍膛。。。1
shootBullet 槍膛為空進入等待。。。。。index=0
PushThread 子彈壓入槍膛。。。2
ShootThread 子彈射出槍膛。。。2
PushThread 子彈壓入槍膛。。。3
ShootThread 子彈射出槍膛。。。3
PushThread 子彈壓入槍膛。。。4
ShootThread 子彈射出槍膛。。。4
shootBullet 槍膛為空進入等待。。。。。index=0
PushThread 子彈壓入槍膛。。。5
ShootThread 子彈射出槍膛。。。5
PushThread 子彈壓入槍膛。。。6
ShootThread 子彈射出槍膛。。。6
shootBullet 槍膛為空進入等待。。。。。index=0
PushThread 子彈壓入槍膛。。。7
ShootThread 子彈射出槍膛。。。7
shootBullet 槍膛為空進入等待。。。。。index=0
PushThread 子彈壓入槍膛。。。8
ShootThread 子彈射出槍膛。。。8
PushThread 子彈壓入槍膛。。。9
ShootThread 子彈射出槍膛。。。9

總結

消費者/生產模式通常用於如下情景:

  • 1.生產與消費共享同一個資源,並且生產者和消費者之間相互依賴,互為條件
  • 2.生產者沒有生產產品前即資源為空時,消費者進入等待,否則通知消費者進行消費
  • 3.消費者消費後要通知生產者已消費完,需要通知生產者進行生產產品以供消費

執行緒直接的協作

- 協作(等待 / 通知機制)情景

是指一個執行緒 A 呼叫了物件 User 的 wait()方法進入等待狀態,而另一個執行緒 B
呼叫了物件User 的 notify()或者 notifyAll()方法,執行緒A 收到通知後從物件User 的wait()方法返回,進而執行後續操作。上述兩個執行緒通過物件User來完成互動,而物件上的 wait()和 notify/notifyAll()的關係就如同開關訊號一樣,用來完成等待方和通知方之間的互動工作。

- notify():
  • 通知一個在物件上等待的執行緒,使其從wait方法返回,而返回的前提是該執行緒
    獲取到了物件的鎖,沒有獲得鎖的執行緒重新進入 WAITING 狀態。
- notifyAll():

通知所有等待在該物件上的執行緒

- wait()

呼叫該方法的執行緒進入 WAITING 狀態,只有等待另外執行緒的通知或被中斷
才會返回.需要注意,呼叫 wait()方法後,會釋放物件的鎖

- wait(long)

超時等待一段時間,這裡的引數時間是毫秒,也就是等待長達n毫秒,如果沒有
通知就超時返回

- wait (long,int)

-對於超時時間更細粒度的控制,可以達到納秒

注意:

以上方法均是java.lang.Object類的方法;
都只能在同步方法或者同步程式碼塊中使用,否則會丟擲異常

等待和通知的標準正規化

等待方遵循如下原則
  1. 獲取物件的鎖
  2. 如果條件不滿足,那麼呼叫的物件的wait()方法,被通知後仍要檢查條件
  3. 條件滿足則執行對應的邏輯
  synchronized(物件){
      while(條件不滿足){
          物件.wait();
      }
      對應的處理邏輯
  }

通知方遵循如下原則

  1. 獲取物件的鎖
  2. 改變條件
  3. 通知所有等待在物件上的執行緒
  synchronized(物件){
      改變條件
          物件.notifyAll();
     
  }

在呼叫 wait () 、notify() 系列方法 之前,執行緒必須要獲得該物件的物件級
別鎖,即只能在同步方法或同步塊中呼叫 wait ()方法 、notify() 系列方法
,進
入 wait()方法後,當前執行緒釋放鎖,在從 wait()返回前,執行緒與其他執行緒競
爭重新獲得鎖,執行 notify()系列方法的執行緒退出呼叫了 notifyAll 的 synchronized
程式碼塊的時候後,他們就會去競爭。如果其中一個執行緒獲得了該物件鎖,它就會
繼續往下執行,在它退出 synchronized 程式碼塊,釋放鎖後,其他的已經被喚醒的
執行緒將會繼續競爭獲取該鎖,一直進行下去,直到所有被喚醒的執行緒都執行完畢

注意事項:

1.notify和notifyAll應該用誰?
  • 儘可能用 notifyall(),謹慎使用 notify(),因為 notify()只會喚醒一個執行緒,我
    們無法確保被喚醒的這個執行緒一定就是我們需要喚醒的執行緒
2.呼叫 yield() 、sleep()、wait()、notify()等方法對鎖有何影響?
  • yield() 、sleep()被呼叫後,都不會釋放當前執行緒所持有的鎖。
  • 呼叫 wait()方法後,會釋放當前執行緒持有的鎖,而且當前被喚醒後,會重新
    去競爭鎖,鎖競爭到後才會執行 wait 方法後面的程式碼。
  • 呼叫 notify()系列方法後,對鎖無影響,執行緒只有在 syn 同步程式碼執行完後才
    會自然而然的釋放鎖,所以 notify()系列方法一般都是 syn 同步程式碼的最後一行