三種方式 解決生產者消費者問題

這裏講解使用三種方式來解決生產者消費者問題

什麼是生產者消費者問題:

        生產者消費者問題(Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共享固定大小緩衝區的線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是生成一定量的數據放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入數據,消費者也不會在緩衝區中空時消耗數據。

生產者消費者問題的解決方案:

   要解決該問題,需要讓生產者在緩衝區滿時休眠,等到消費者消耗緩衝區中的數據的時候,生產者才能被喚醒,開始往緩衝區添加數據。同樣,消費者在緩衝區空時也進入休眠,等到生產者往緩衝區添加數據之後,再喚醒消費者。通常採用進程間通信的方法解決該問題,常用的方法有等待-喚醒wait-notifyAll、阻塞隊列BlockingQueue,信號量Semaphore等。

下面講解wait-notifyAll、BlockingQueue、Semaphore三種方式的實現過程

一、wait-notifyAll通過對控制產品隊列的對象鎖的方式,讓線程進行等待和喚醒來解決生產者消費者問題。

代碼如下:

首先是生產者代碼Producer.java

package com.interview.thread.producerAndConsumer.waitAndNotify;

import java.util.List;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 14:46 2019/1/13
 */
public class Producer implements Runnable{
    //產品隊列
    private List<String> storage;
    //隊列最大容量
    private int maxCap;

    public Producer(List storage,int maxCap){
        this.storage = storage;
        this.maxCap = maxCap;
    }
    //生產方法
    public void produce(){
        String product = "產品"+(int)(Math.random()*10000);
        System.out.println(Thread.currentThread().getName()+"我生產了一件產品"+product);
        storage.add(product);
    }

    @Override
    public void run() {
        while (true){
            synchronized (storage){
                while (storage.size() == maxCap){
                    System.out.println("storage is full");
                    try {
                        //如果產品隊列滿了,則讓生產者線程等待
                        storage.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                produce();
                //喚醒所有等待線程
                storage.notifyAll();
            }
        }

    }
}

消費者代碼consumer.java

package com.interview.thread.producerAndConsumer.waitAndNotify;

import java.util.List;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 14:46 2019/1/13
 */
public class Consumer implements Runnable{
    //產品隊列
    private List<String> storage;
    //產品隊列最大容量
    private int maxCap;

    public Consumer(List storage,int maxCap){
        this.storage = storage;
        this.maxCap = maxCap;
    }

    private void consume(){
        System.out.println(Thread.currentThread().getName()+"我消費了一件商品"+storage.get(0));
        storage.remove(0);
    }

    @Override
    public void run() {
        while (true){
            synchronized (storage){
                if (storage.isEmpty()){
                    System.out.println("storage is empty");
                    try {
                        //如果產品隊列爲空,則讓消費者線程等待
                        storage.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                consume();
                //喚醒所有等待線程
                storage.notifyAll();
            }
        }


    }
}

測試類代碼,Test.java

package com.interview.thread.producerAndConsumer.waitAndNotify;

import java.util.LinkedList;
import java.util.List;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 14:58 2019/1/13
 */
public class Test {

    public static void main(String[] args) {
        //產品隊列,當然使用Queue也是可以的
        List<String> storage = new LinkedList<>();
        int maxCap = 10;
        //生產者
        Producer producer = new Producer(storage,maxCap);
        //消費者
        Consumer consumer = new Consumer(storage,maxCap);

        //創建生產者消費者線程
        Thread threadProducer = new Thread(producer,"threadProducer");
        Thread threadProducer1 = new Thread(producer,"threadProducer1");
        Thread threadProducer2 = new Thread(producer,"threadProducer2");
        Thread threadConsumer = new Thread(consumer,"threadConsumer");
        Thread threadConsumer1 = new Thread(consumer,"threadConsumer1");

        //執行生產者消費者線程的start方法啓動線程
        threadProducer.start();
        threadConsumer1.start();
        threadProducer2.start();

        threadProducer1.start();
        threadConsumer.start();


    }

}

二、使用BlockingQueue阻塞隊列的方式實現,阻塞隊列當阻塞隊列滿的時候會讓生產者線程阻塞,當阻塞隊列爲空的時候會讓消費者線程阻塞。代碼實現如下:

首先是生產者,producer.java

package com.interview.thread.producerAndConsumer.blockingQueue;

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

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 17:11 2019/1/13
 */
public class Consumer implements Runnable {
    private BlockingQueue blockingQueue;

    public Consumer(BlockingQueue blockingQueue){
        this.blockingQueue = blockingQueue;
    }

    private void consume() {
        String product = null;
        try {
            product = (String) blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"我消費了一件商品"+product);
    }

    @Override
    public void run() {
        while (true){
            consume();
        }
    }
}

消費者 Conssumer.java

package com.interview.thread.producerAndConsumer.blockingQueue;

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

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 17:05 2019/1/13
 */
public class Producer implements Runnable {
    //阻塞隊列
    private BlockingQueue blockingQueue;

    public Producer(BlockingQueue blockingQueue){
        this.blockingQueue = blockingQueue;
    }

    private void produce() {
        String product = "產品"+(int)(Math.random()*10000);
        System.out.println(Thread.currentThread().getName()+"我生產了一件產品"+product);
        try {
            blockingQueue.put(product);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true){
            produce();
        }
    }
}

測試類代碼Test.java

package com.interview.thread.producerAndConsumer.blockingQueue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 17:02 2019/1/13
 */
public class Test {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        int maxCap = 10;
        BlockingQueue blockingQueue = new LinkedBlockingQueue(maxCap);

        Producer producer = new Producer(blockingQueue);
        Consumer consumer = new Consumer(blockingQueue);

        Thread producerThread = new Thread(producer,"producerThread");
        Thread producerThread1 = new Thread(producer,"producerThread1");

        Thread consumerThread = new Thread(consumer,"consumerThread");
        Thread consumerThread1 = new Thread(consumer,"consumerThread1");

        producerThread.start();
        consumerThread.start();

        producerThread1.start();
        consumerThread1.start();

    }

}

三、使用Semaphore信號量的方式實現,Semaphore方式通過令牌控制可進入產品隊列的生產者和消費者,以達到解決生產者消費者的問題。代碼如下:

我們先看測試類Test.java

package com.interview.thread.producerAndConsumer.semaphore;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 19:45 2019/1/13
 */
public class Test {

    public static void main(String[] args) {
        int maxCap = 10;                            //產品隊列的最大容量
        Queue<String> queue = new LinkedList();     //產品隊列
        //初始化一個爲0的信號量,只允許release,不允許acquire,當emptySemaphore被release後則可以acquire
        Semaphore emptySemaphore = new Semaphore(0);
        //初始化一個爲maxCap的信號量作爲隊列中生產者線程的信號量,在不release的前提下最多允許被maxCap個線程acquire到
        Semaphore fullSemaphore = new Semaphore(maxCap);
        //同步信號量,同時只能被一個線程acquire到,用來產品隊列的修改能同步進行
        Semaphore mutexSemaphore = new Semaphore(1);

        //創建生產者消費者實例
        Producer producer = new Producer(queue,emptySemaphore,fullSemaphore,mutexSemaphore);
        Consumer consumer = new Consumer(queue,emptySemaphore,fullSemaphore,mutexSemaphore);
        //創建線程
        Thread producerThread = new Thread(producer,"producerThread");
        Thread producerThread1 = new Thread(producer,"producerThread1");
        Thread consumerThread = new Thread(consumer,"consumerThread");
        Thread consumerThread1 = new Thread(consumer,"consumerThread1");
        //啓動線程
        producerThread.start();
        producerThread1.start();
        consumerThread.start();
        consumerThread1.start();
    }

}

然後是生產者類producer.java

package com.interview.thread.producerAndConsumer.semaphore;

import java.util.Queue;
import java.util.concurrent.Semaphore;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 19:20 2019/1/13
 */
public class Producer implements Runnable {
    private Queue queue;
    private Semaphore emptySemaphore ;
    private Semaphore fullSemaphore ;
    private Semaphore mutexSemaphore ;

    public Producer(Queue queue,Semaphore emptySemaphore,Semaphore fullSemaphore,Semaphore mutexSemaphore){
        this.queue = queue;
        this.emptySemaphore = emptySemaphore;
        this.fullSemaphore = fullSemaphore;
        this.mutexSemaphore = mutexSemaphore;
    }

    private void produce(){
        System.out.println("當前產品數:"+queue.size());
        String product = "產品"+(int)(Math.random()*10000);
        System.out.println(Thread.currentThread().getName()+"我生產了一件產品"+product);
        queue.offer(product);
    }

    @Override
    public void run() {
        while (true){
            try {
                //生產產品時獲取一個令牌(總數爲maxCap個),並佔有此令牌,在產品被消費時此令牌纔會被釋放
                fullSemaphore.acquire();
                //獲取同步信號量的令牌(總數爲1),所以來保證對產品隊列的操作能同步進行
                mutexSemaphore.acquire();
                //產品生產過程
                produce();
                //釋放同步信號量令牌
                mutexSemaphore.release();
                //產品生產過程結束,釋放emptySemaphore信號量的令牌,讓消費者可以獲取此信號量的令牌進行消費
                emptySemaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消費者類Consumer.java

package com.interview.thread.producerAndConsumer.semaphore;

import java.util.Queue;
import java.util.concurrent.Semaphore;

/**
 * @Author: gtd
 * @Description:
 * @Date: Create in 19:36 2019/1/13
 */
public class Consumer implements Runnable {
    private Queue queue;
    private Semaphore emptySemaphore ;
    private Semaphore fullSemaphore ;
    private Semaphore mutexSemaphore ;

    public Consumer(Queue queue,Semaphore emptySemaphore,Semaphore fullSemaphore,Semaphore mutexSemaphore){
        this.queue = queue;
        this.emptySemaphore = emptySemaphore;
        this.fullSemaphore = fullSemaphore;
        this.mutexSemaphore = mutexSemaphore;
    }

    private void consume() {
        System.out.println("當前產品數:"+queue.size());
        String product = (String) queue.poll();
        System.out.println(Thread.currentThread().getName()+"我消費了一件產品"+product);
    }

    @Override
    public void run() {
        while (true){
            try {
                //獲取emptySemaphore信號量的令牌,當此信號量未被release釋放前,該過程一直處於阻塞狀態
                emptySemaphore.acquire();
                //獲取同步信號量的執行令牌
                mutexSemaphore.acquire();
                //執行消費者的消費過程
                consume();
                //釋放同步信號量的執行令牌
                mutexSemaphore.release();
                //釋放fullSemaphore信號量,代表產品隊列中的一個產品被消費了,fullSemaphore中的可用令牌數目+1
                fullSemaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

代碼中註釋講的比較清楚,使用Semaphore通過控制可使用的令牌數目已達到對產品隊列同步操作以及以及阻塞線程的目的來解決生產者消費者的問題。

代碼源碼路徑爲:https://github.com/tiedungao/interviewDesignModelProject

如果在閱讀過程中發現有什麼錯誤、不足或自己的觀點,歡迎提出。

 

 

 

 

 

 

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