Java多線程技術~生產者和消費者問題

Java多線程技術~生產者和消費者問題

本文是上一篇文章的後續,詳情點擊該連接

線程通信

應用場景:生產者和消費者問題

       假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費

       如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走爲止

       如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品爲止

分析

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

       對於生產者,沒有生產產品之前,要通知消費者等待。而生產了產品之後,又需要馬上通知消費者消費

       對於消費者,在消費之後,要通知生產者已經消費結束,需要繼續生產新產品以供消費

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

              synchronized可阻止併發更新同一個共享資源,實現了同步

              synchronized不能用來實現不同線程之間的消息傳遞(通信)

方法名 作用
final void wait() 表示線程一直等待,直到其它線程通知
void wait(long timeout) 線程等待指定毫秒參數的時間
final void wait(long timeout,int nanos) 線程等待指定毫秒、微妙的時間
final void notify() 喚醒一個處於等待狀態的線程
final void notifyAll() 喚醒同一個對象上所有調用wait()方法的線程,優先級別高的線程優先運行

消費者,生產者線程代碼實現

package com.alvin.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    public static void main(String[] args){
        //創建共享資源對象
        Shop shop = new Shop();
        //創建生產者
        Boss boss = new Boss(shop);
        //創建消費者
        Customer customer = new Customer(shop);
        //啓動線程
        new Thread(boss).start();
        new Thread(customer).start();
    }
}

//生產者類
class Boss implements Runnable{
    private Shop shop;
    public Boss(Shop shop){
        this.shop = shop;
    }
    @Override
    public void run() {
        for(int i = 1; i < 100; i++){
            if(i % 2 == 0){
                shop.Set("OPPO","reno 3 Pro");
            }else{
                shop.Set("HUWEI","Mate 30 Pro");
            }
        }
    }
}

//消費者類
class Customer implements Runnable{
    private Shop shop;
    public Customer(Shop shop){
        this.shop = shop;
    }
    @Override
    public void run() {
        for(int i = 1; i < 100; i++){
            shop.Get();
        }
    }
}

//商品類
class Shop{
    private String brand;       //商品品牌
    private String name;        //商品名稱
    private boolean flag = false; //默認沒有商品

  // 編寫一個賦值的方法  同步監視器爲Shop類的對象
    public synchronized void Set(String brand, String name){
        //先判斷下有沒有商品
        if(flag){
            try {
                //生產者線程等待
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //當生產者線程被喚醒後從wait()之後的代碼開始執行
        //生產商品
        this.brand = brand;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
        System.out.println("老闆: 今天廠裏生產了 " + brand + " " + name + " 歡迎下次光臨!----------");
        //通知消費者
        super.notify();
        flag = true;
    }

    //編寫一個取值的方法
    public synchronized void Get(){
        //先判斷下有沒有商品
        if(!flag){
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("----------顧客: " + brand + " " + name + " 已經買到手,謝謝老闆!");
        //通知生產者
        super.notify();
        flag = false;
    }
}

線程同步的細節

       進行線程通信的多個線程,要使用同一個同步監視器(product),還必須要調用該同步監視器的wait()、notify()、notifyAll();

wait()等待

       在【其他線程】調用【此對象】的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。換句話說,此方法的行爲就好像它僅執行 wait(0) 調用一樣。當前線程必須擁有此對象監視器

wait(time) 等待

       在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,導致當前線程等待。 當前線程必須擁有此對象監視器。

notify() 通知 喚醒

       喚醒在【此對象監視器】上等待的【單個】線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。【選擇是任意性的】,並在對實現做出決定時發生

notifyAll() 通知所有 喚醒所有

       喚醒在【此對象監視器】上等待的【所有】線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程【進行競爭】;

完整的線程生命週期

阻塞狀態有三種

       普通的阻塞 sleep,join,Scanner input.next()

       同步阻塞(鎖池隊列) 沒有獲取同步監視器的線程的隊列

       等待阻塞(阻塞隊列) 被調用了wait()後釋放鎖,然後進行該隊列

sleep()和wait()的區別

       區別1:sleep() 線程會讓出CPU進入阻塞狀態,但不會釋放對象鎖 wait() 線程會讓出CPU進入阻塞狀態, 【也會放棄對象鎖】,進入等待【此對象】的等待鎖定池

       區別2: 進入的阻塞狀態也是不同的隊列

       區別3:wait只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用

使用新一代的Lock實現線程通信

       之前實現線程通信時,是生產者和消費者在一個等待隊列中,會存在本來打算喚醒消費者,卻喚醒一個生產者的問題,能否讓生產者和消費者線程在不同的隊列中等待呢?在新一代的Lock中提供這種實現方式。

package com.alvin.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    public static void main(String[] args){
        //創建共享資源對象
        Shop shop = new Shop();
        //創建生產者
        Boss boss = new Boss(shop);
        //創建消費者
        Customer customer = new Customer(shop);
        //啓動線程
        new Thread(boss).start();
        new Thread(customer).start();
    }
}

//生產者類
class Boss implements Runnable{
    private Shop shop;
    public Boss(Shop shop){
        this.shop = shop;
    }
    @Override
    public void run() {
        for(int i = 1; i < 100; i++){
            if(i % 2 == 0){
                shop.Set("OPPO","reno 3 Pro");
            }else{
                shop.Set("HUWEI","Mate 30 Pro");
            }
        }
    }
}

//消費者類
class Customer implements Runnable{
    private Shop shop;
    public Customer(Shop shop){
        this.shop = shop;
    }
    @Override
    public void run() {
        for(int i = 1; i < 100; i++){
            shop.Get();
        }
    }
}

//商品類
class Shop{
    private String brand;       //商品品牌
    private String name;        //商品名稱
    private boolean flag = false;//默認沒有商品

    private Lock lock = new ReentrantLock();
    Condition boss = lock.newCondition();       //用於生產者
    Condition customer = lock.newCondition();   //用於消費者

    // 編寫一個賦值的方法  同步監視器爲Shop類的對象
    public  void Set(String brand, String name){
        lock.lock();
        try {
            //先判斷下有沒有商品
            if (flag) {
                try {
                    //生產者線程等待
                    boss.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //當生產者線程被喚醒後從wait()之後的代碼開始執行
            //生產商品
            this.brand = brand;
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.name = name;
            System.out.println("老闆: 今天廠裏生產了 " + brand + " " + name + " 歡迎下次光臨!----------");
            //通知消費者
            customer.signalAll();
            flag = true;
        }finally {
            //解鎖
            lock.unlock();
        }
    }

    //編寫一個取值的方法
    public  void Get(){
        lock.lock();
        try {
            //先判斷下有沒有商品
            if (!flag) {
                try {
                    //消費者等待
                    customer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("----------顧客: " + brand + " " + name + " 已經買到手,謝謝老闆!");
            //通知生產者
            boss.signalAll();
            flag = false;
        }finally {
            //解鎖
            lock.unlock();
        }
    }
}

Condition

       Condition是在Java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現線程間協作更加安全和高效。

       它的更強大的地方在於:能夠更加精細的控制多線程的休眠與喚醒。對於同一個鎖,我們可以創建多個Condition,在不同的情況下使用不同的Condition 一個Condition包含一個等待隊列。一個Lock可以產生多個Condition,所以可以有多個等待隊列。

       在Object的監視器模型上,一個對象擁有一個同步隊列和等待隊列,而Lock(同步器)擁有一個同步隊列和多個等待隊列。

       Object中的wait(),notify(),notifyAll()方法是和"同步鎖"(synchronized關鍵字)捆綁使用的;而Condition是需要與"互斥鎖"/"共享鎖"捆綁使用的。

       調用Condition的await()、signal()、signalAll()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間纔可以使用


       Conditon中的await()對應Object的wait();

       Condition中的signal()對應Object的notify();

       Condition中的signalAll()對應Object的notifyAll()。

void await() throws InterruptedException

       造成當前線程在接到信號或被中斷之前一直處於等待狀態。 與此 Condition 相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下四種情況之一 以前,當前線程將一直處於休眠狀態。

       其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者 其他某個線程調用此 Condition 的 signalAll() 方法;或者其他某個線程中斷當前線程,且支持中斷線程的掛起;或者發生“虛假喚醒”

       在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證它保持此鎖。

void signal()

       喚醒一個等待線程。

       如果所有的線程都在等待此條件,則選擇其中的一個喚醒。在從 await 返回之前,該線程必須重新獲取鎖。

void signalAll()

       喚醒所有等待線程。

       如果所有的線程都在等待此條件,則喚醒所有線程。在從 await 返回之前,每個線程都必須重新獲取鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章