三种方式 解决生产者消费者问题

这里讲解使用三种方式来解决生产者消费者问题

什么是生产者消费者问题:

        生产者消费者问题(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

如果在阅读过程中发现有什么错误、不足或自己的观点,欢迎提出。

 

 

 

 

 

 

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