生產者消費者模式java的三種實現

生產者消費者

介紹

優點

  • 可以解耦生產者和消費者,互相之間不會產生影響
  • 支持併發操作,生產者只管生產數據,生產的數據放到緩衝區中,而不需要等消費者消費完再生產下一個數據,不會造成阻塞
  • 支持忙閒不均

數據單元

  • 特性
    • 關聯到業務對象
    • 完整性
    • 獨立性
    • 顆粒度

設計

  • 緩衝區一般使用阻塞隊列,當隊列滿時會阻塞生產者繼續生產數據,直到有消費者來消費了數據。當隊列爲空時也會阻塞消費者繼續消費
  • 生產消費者問題是個非常典型的多線程問題,涉及到的對象包括“生產者”,“消費者”,“倉庫”,“產品”
  • 關係
    • 生產者只有在倉庫未滿時生產,倉滿就停止生產
    • 消費者僅僅在倉庫有產品的時候才能消費,倉空就等待
    • 當消費者發現倉庫沒產品消費的時候通知生產者生產
    • 生產者在生成出產品後通知等待的消費者去消費
  • wait/notify方法
    • sleep()是Thread類的方法,而wait(),notify(),notifyAll()是Object類中定義的方法,儘管這兩個方法都會影響線程的執行行爲,但是本質上是有區別的
    • Thread.sleep()不會導致鎖行爲的改變,可以簡單的認爲,與鎖相關的方法都定義在Object類中,因此調用Thread.sleep()是不會影響鎖的相關行爲
    • Thread.sleep和object.wait都會暫停當前的線程,對於CPU資源來說,不管是哪種方式暫停線程,都表示它暫時不需要CPU的執行時間,OS會將執行時間分配給其他線程。區別是調用wait後,需要別的線程執行notify/notifyAll才能重新獲得CPU執行時間

實現生產者消費者模型

  • BlockingQueue阻塞隊列方法
  • Object的wait()/notify()方法
  • Lock和Condition的await()/signal()方法

java實現

方法一:使用BlockingQueue阻塞隊列方法

數據類Data

public class Data {
    private int id;
    //生產量
    private int num;
    public Data(int id,int num){
        this.id=id;
        this.num=num;
    }
    public Data(){

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

生產者

import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Producer implements Runnable {
    //共享阻塞隊列
    private BlockingDeque<Data> queue;
    //是否還在運行
    private volatile boolean isRunning = true;
    //id生成器原子操作
    private static AtomicInteger count = new AtomicInteger();
    // 生成隨機數
    private static Random random = new Random();
    public Producer(BlockingDeque<Data> queue){
        this.queue=queue;
    }
    @Override
    public void run() {
        try{
            while(isRunning){
                // 模擬生產耗時
                Thread.sleep(random.nextInt(1000));
                int num=count.incrementAndGet();
                Data data=new Data(num,num);
                System.out.println("當前>>生產者:"+Thread.currentThread().getName()+"生產量"+num);
                if(!queue.offer(data,2, TimeUnit.SECONDS)){
                    System.out.println("生產失敗...");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public void stop(){
        isRunning=false;
    }
}

消費者

import java.util.Random;
import java.util.concurrent.BlockingDeque;

public class Consumer implements  Runnable {
    //雙端隊列,加入或者取出元素都是線程安全的
    private BlockingDeque<Data> queue;
    private static Random random=new  Random();
    public Consumer(BlockingDeque<Data> queue){
        this.queue=queue;
    }
    @Override
    public void run(){
        while (true){
            try{
                // 檢索並刪除,如果需要等待、直到元素可用。
                Data data= queue.take();
                //模擬消費耗時
                Thread.sleep(random.nextInt(1000));
                if(data!=null){
                    System.out.println("當前<<消費者:"+Thread.currentThread().getName()+",消費量"+data.getNum());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

測試

import java.util.concurrent.*;

public class TestPro {
    public static void main(String[] args) throws InterruptedException{
        BlockingDeque<Data> queue = new LinkedBlockingDeque<>(10);

        Producer producer1 = new Producer(queue);
        Producer producer2 = new Producer(queue);
        Producer producer3 = new Producer(queue);

        Consumer consumer1 = new Consumer(queue);
        Consumer consumer2 = new Consumer(queue);
        Consumer consumer3 = new Consumer(queue);

        ExecutorService service= Executors.newCachedThreadPool();
        service.execute(producer1);
        service.execute(producer2);
        service.execute(producer3);
        service.execute(consumer1);
        service.execute(consumer2);
        service.execute(consumer3);

        Thread.sleep(3000);
        producer1.stop();
        producer2.stop();
        producer3.stop();

        Thread.sleep(1000);
        service.shutdown();
    }
}

結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hBNwcyGG-1582708080156)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226105053.png)]

最後一次生產20此時所有的生產者都停止生產了,但是此時產品池還沒空,於是消費者繼續消費,直到把產品池中的數據消耗完

方法二:使用Object的wait()/notify()方法

wait()/ nofity()方法是基類Object的兩個方法,也就意味着所有Java類都會擁有這兩個方法,這樣,我們就可以爲任何對象實現同步機制。

  • wait():當緩衝區已滿/空時,生產者/消費者線程停止自己的執行,放棄鎖,使自己處於等待狀態,讓其他線程執行。
  • notify():當生產者/消費者向緩衝區放入/取出一個產品時,向其他等待的線程發出可執行的通知,同時放棄鎖,使自己處於等待狀態。

生產者

import java.util.Queue;
import java.util.Random;

public class Producer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;
    int i=0;
    public Producer(String name,Queue<Integer> queue,int maxSize){
        super(name);
        this.name=name;
        this.queue=queue;
        this.maxSize=maxSize;
    }
    @Override
    public void run(){
        while (true){
            synchronized (queue){
                while (queue.size()==maxSize){
                    try{
                        System.out.println("隊列已經滿了,生產者["+name+"]線程等待"+"消費者從隊列中消費產品。");
                        queue.wait();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                System.out.println("[" + name + "] 生產產品  : +" + i);
                queue.offer(i++);
                queue.notifyAll();
                try{
                    Thread.sleep(new Random().nextInt(1000));
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

消費者

import java.util.Queue;
import java.util.Random;

public class Consumer extends Thread {
    private Queue<Integer> queue;
    String name;
    int maxSize;
    public Consumer(String name,Queue<Integer>queue,int maxSize){
        super(name);
        this.name=name;
        this.queue=queue;
        this.maxSize=maxSize;
    }
    @Override
    public void run(){
        while (true){
            synchronized (queue){
                while (queue.isEmpty()){
                    try{
                        System.out.println("隊列是空的 消費者[" + name + "] 等待生產者生產");
                        queue.wait();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                int x=queue.poll();
                System.out.println("[" + name + "] 消費產品 : " + x);
                queue.notifyAll();
                try{
                    Thread.sleep(new Random().nextInt(1000));
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

生產者消費者模式

import java.util.LinkedList;
import java.util.Queue;

/**
 * 生產者消費者模式:使用Object.wait()/notify()方法實現
 */
public class ProdicerConsumer {
    private static final int CAPACITY = 500;

    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<Integer>();
        Thread producer1 = new Producer("P-1", queue, CAPACITY);
        Thread producer2 = new Producer("P-2", queue, CAPACITY);
        Thread consumer1 = new Consumer("C1", queue, CAPACITY);
        Thread consumer2 = new Consumer("C2", queue, CAPACITY);
        Thread consumer3 = new Consumer("C3", queue, CAPACITY);

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }
}

結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2s2ckWoo-1582708080157)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226160351.png)]

注意要點

  • 判斷Queue大小爲0或者大於queueSize時須使用 while (condition) {},不能使用 if(condition) {}。其中 while(condition)循環,它又被叫做**“自旋鎖”。爲防止該線程沒有收到notify()調用也從wait()中返回(也稱作虛假喚醒**),這個線程會重新去檢查condition條件以決定當前是否可以安全地繼續執行還是需要重新保持等待,而不是認爲線程被喚醒了就可以安全地繼續執行了。

方法三:使用Lock和Condition的await() / signal()方法

在JDK5.0之後,Java提供了更加健壯的線程處理機制,包括同步、鎖定、線程池等,它們可以實現更細粒度的線程控制。Condition接口的await()signal()就是其中用來做同步的兩種方法,它們的功能基本上和Object的wait()/ nofity()相同,完全可以取代它們,但是它們和新引入的鎖定機制Lock直接掛鉤,具有更大的靈活性。通過在Lock對象上調用newCondition()方法,將條件變量和一個鎖對象進行綁定,進而控制併發程序訪問競爭資源的安全。

生產者消費者模式:使用Lock和Condition實現


import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerByLock {
    private static final int CAPACITY = 5;
    private static final Lock lock = new ReentrantLock();
    private static final Condition fullCondition = lock.newCondition();     //隊列滿的條件
    private static final Condition emptyCondition = lock.newCondition();        //隊列空的條件


    public static void main(String args[]){
        Queue<Integer> queue = new LinkedList<Integer>();

        Thread producer1 = new Producer("P-1", queue, CAPACITY);
        Thread producer2 = new Producer("P-2", queue, CAPACITY);
        Thread consumer1 = new Consumer("C1", queue, CAPACITY);
        Thread consumer2 = new Consumer("C2", queue, CAPACITY);
        Thread consumer3 = new Consumer("C3", queue, CAPACITY);

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }

    /**
     * 生產者
     */
    public static class Producer extends Thread{
        private Queue<Integer> queue;
        String name;
        int maxSize;
        int i = 0;

        public Producer(String name, Queue<Integer> queue, int maxSize){
            super(name);
            this.name = name;
            this.queue = queue;
            this.maxSize = maxSize;
        }

        @Override
        public void run(){
            while(true){

                //獲得鎖
                lock.lock();
                while(queue.size() == maxSize){
                    try {
                        System.out .println("隊列已經滿了,生產者["+name+"]線程等待"+"消費者從隊列中消費產品。");
                        //條件不滿足,生產阻塞
                        fullCondition.await();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                System.out.println("[" + name + "] 生產產品  : +" + i);
                queue.offer(i++);

                //喚醒其他所有生產者、消費者
                fullCondition.signalAll();
                emptyCondition.signalAll();

                //釋放鎖
                lock.unlock();

                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 消費者
     */
    public static class Consumer extends Thread{
        private Queue<Integer> queue;
        String name;
        int maxSize;

        public Consumer(String name, Queue<Integer> queue, int maxSize){
            super(name);
            this.name = name;
            this.queue = queue;
            this.maxSize = maxSize;
        }

        @Override
        public void run(){
            while(true){
                //獲得鎖
                lock.lock();

                while(queue.isEmpty()){
                    try {
                        System.out.println("隊列是空的 消費者[" + name + "] 等待生產者生產");
                        //條件不滿足,消費阻塞
                        emptyCondition.await();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                int x = queue.poll();
                System.out.println("[" + name + "] 消費產品 : " + x);

                //喚醒其他所有生產者、消費者
                fullCondition.signalAll();
                emptyCondition.signalAll();

                //釋放鎖
                lock.unlock();

                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QNVflgMO-1582708080158)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226164915.png)]

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