Prática de Programação Simultânea Java (7) [Produtor-Consumidor]

produtor-consumidor 

Visão geral

  • O problema produtor-consumidor, também conhecido como problema de buffer limitado, é um caso clássico de problema de sincronização multithread. A questão descreve dois threads compartilhando um buffer de tamanho fixo
  • No desenvolvimento multithread, se o produtor (thread que produz dados) processa rapidamente e o consumidor (thread que consome dados) processa muito lentamente, então o produtor deve esperar que o consumidor termine o processamento antes de continuar a produzir dados. Se o poder de processamento do consumidor for maior que o do produtor, então o consumidor deverá esperar pelo produtor. Para resolver este problema de capacidades desequilibradas de produção e consumo, surgiu o modelo de produtor e consumidor

Implementação

O padrão produtor-consumidor resolve o forte problema de acoplamento entre produtores e consumidores através de um contêiner. Produtores e consumidores não se comunicam diretamente entre si, mas sim por meio de filas de bloqueio . Depois que o produtor produz os dados, ele não precisa esperar que o consumidor os processe, mas os joga diretamente na fila de bloqueio.O consumidor não solicita dados ao produtor, mas os retira diretamente da fila de bloqueio. Portanto, em um cenário simultâneo, quando multithreads operam em recursos de seção crítica (ou seja, recursos compartilhados), eles devem garantir que apenas um thread possa existir durante a leitura e a gravação, portanto, uma estratégia de bloqueio precisa ser projetada.

implementação sincronizada + aguardar/notificar

package com.bierce.multiThread;
import java.time.Instant;
import java.util.LinkedList;
import java.util.Random;
public class TestProducerConsumer1 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个自定义的Sy阻塞队列,其中存储整型的个数为10
        MySnchronizedBlockingQueueS mySnchronizedBlockingQueueS = new MySnchronizedBlockingQueueS(10);
        int resourceCount = mySnchronizedBlockingQueueS.size(); //阻塞队列资源个数
        //创建生产者线程
        Runnable producer=()->{
            while (resourceCount < 1){
                try {
                    int random = new Random().nextInt(100); //生成一个0-100的两位整数
                    mySnchronizedBlockingQueueS.put(random);
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "生产了一个整型数据==>"+random + ",当前资源池有"+mySnchronizedBlockingQueueS.size()+"个资源");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }};
        for (int i = 1; i <= 5; i++) { //创建5个生产线程 线程名称从0-4
            new Thread(producer).start();
        }
        //创建消费者线程
        Runnable consumer=()->{
            while (true){
                try {
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "消费了一个整型数据==>"+mySnchronizedBlockingQueueS.take() + ",当前资源池有"+mySnchronizedBlockingQueueS.size()+"个资源");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }};
        for (int i = 1; i <= 5; i++) { //创建5个消费线程 线程名称从5-9
            new Thread(consumer).start();
        }
    }
}
/***
 *
 *  自定义阻塞队列: 通过Synchronized + wait/notifyAll实现
 * @author Bierce
 * @date 2023/08/15
 */
class MySnchronizedBlockingQueueS {
    private final int maxSize; //容器允许存放的最大数量
    private final LinkedList<Integer> container; //存储数据的容器
    public MySnchronizedBlockingQueueS(int maxSize ) {
        this.maxSize = maxSize;
        this.container = new LinkedList<>();
    }
    /**
     *  往队列添加元素,如果队列已满则阻塞线程
     */
    public  synchronized  void put(Integer data){
        //如果队列已满,则阻塞生产者线程
        while (container.size()==maxSize){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //队列未满则添加元素,并通知消费者消费数据
        container.add(data);
        notifyAll();
    }
    /**
     *  从队列取出数据,如果队列为空则阻塞
     * @return  队列元素
     */
    public synchronized  Integer take(){
        //如果队列为空,则消费者停止消费
        while (container.size()==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //队列不为空则消费数据,并通知生产者继续生产数据
        int data = container.poll();
        notifyAll();
        return data;
    }
    public int size(){
        return container.size();
    }
}

sincronizado não pode obter o efeito de notificação precisa, mas Condição pode notificar com precisão qual thread deve ser despertado. 

Implementação de bloqueio + condição

package com.bierce.multiThread;
import java.time.Instant;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestProducerConsumer2 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个自定义的阻塞队列,其中存储整型的个数为10
        MyBlockingQueue<Integer> myBlockingQueue = new MyBlockingQueue<>(10, false);
        int resourceCount = myBlockingQueue.size(); //阻塞队列资源个数
        //创建生产者线程
        Runnable producer=()->{
            while (resourceCount < 1){
                try {
                    int random = new Random().nextInt(100); //生成一个0-100的两位整数
                    myBlockingQueue.put(random);
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "生产了一个整型数据==>"+random + ",当前资源池有"+myBlockingQueue.size()+"个资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }};

        for (int i = 1; i <= 5; i++) { //创建5个生产线程 线程名称从0-4
            new Thread(producer).start();
        }
        //创建消费者线程
        Runnable consumer=()->{
            while (true){
                try {
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "消费了一个整型数据==>"+myBlockingQueue.take() + ",当前资源池有"+myBlockingQueue.size()+"个资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }};
        for (int i = 1; i <= 5; i++) { //创建5个消费线程 线程名称从5-9
            new Thread(consumer).start();
        }
    }
}
/**
 *自定义阻塞队列: Lock+Condition实现
 */
class MyBlockingQueue<E> {
    private final Queue queue; //队列容器
    private final int capacity; //队列容量
    final ReentrantLock lock; //对象锁
    private final Condition notEmpty; //等待取出数据条件
    private final Condition notFull; //等待添加数据条件
    /**
     * 初始化阻塞队列
     * @param capacity  队列容量
     * @param fair  是否公平锁
     */
    public MyBlockingQueue(int capacity, boolean fair) {
        this.queue = new LinkedList();
        this.capacity=capacity;
        this.lock = new ReentrantLock(fair);
        this.notEmpty = lock.newCondition();
        this.notFull =  lock.newCondition();
    }
    /**
     *   往队列插入元素,如果队列大小到达容量限制则阻塞
     * @param e 插入元素
     * @throws InterruptedException 中断异常
     */
    public  void put(E e) throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lock(); //上锁
        try{
            while (queue.size()==capacity){ //队列已满则阻塞
                notFull.await();
            }
            //队列未满则加入数据并唤醒消费者进行消费
            queue.add(e);
            notEmpty.signalAll();
        } finally {
            lock.unlock(); //必须释放锁
        }
    }
    /**
     *   从队列取出一个元素,如果队列为空则阻塞
     * @return 队列元素
     * @throws InterruptedException 中断异常
     */
    public  E take()throws  InterruptedException{
        final ReentrantLock lock = this.lock;
        lock.lock();
        try{
            while (queue.size()==0){ //队列为空则阻塞
                notEmpty.await();
            }
            //队列有数据则获取数据并唤醒生产者进行生产
            E element = (E) queue.remove();
            notFull.signalAll();
            return   element;
        } finally {
            lock.unlock(); //必须释放锁
        }
    }
    public int size(){
        return queue.size();
    }
}
Personalizar a saída do console myBlockingQueue

Implementação de fila de bloqueio BlockingQueue

public void put(E e) throws InterruptedException {
	checkNotNull(e);
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();
	try {
		while (count == items.length)
			notFull.await();
		enqueue(e);
	} finally {
		lock.unlock();
	}
}
public E take() throws InterruptedException {
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();
	try {
		while (count == 0)
			notEmpty.await();
		return dequeue();
	} finally {
		lock.unlock();
	}
}
/**
 * Inserts element at current put position, advances, and signals.
 * Call only when holding lock.
 */
private void enqueue(E x) {
	// assert lock.getHoldCount() == 1;
	// assert items[putIndex] == null;
	final Object[] items = this.items;
	items[putIndex] = x;
	if (++putIndex == items.length)
		putIndex = 0;
	count++;
	notEmpty.signal();
}
/**
 * Extracts element at current take position, advances, and signals.
 * Call only when holding lock.
 */
private E dequeue() {
	// assert lock.getHoldCount() == 1;
	// assert items[takeIndex] != null;
	final Object[] items = this.items;
	@SuppressWarnings("unchecked")
	E x = (E) items[takeIndex];
	items[takeIndex] = null;
	if (++takeIndex == items.length)
		takeIndex = 0;
	count--;
	if (itrs != null)
		itrs.elementDequeued();
	notFull.signal();
	return x;
}

De acordo com o código-fonte dos métodos put e take de ArrayBlockingQueue, pode-se observar que o mecanismo subjacente ainda é Lock+condition.

package com.bierce.multiThread;
import java.time.Instant;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class TestProducerConsumer3 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个阻塞队列,其中存储整型的个数为10
        BlockingQueue<Integer> queue= new ArrayBlockingQueue<>(10);
        int resourceCount = queue.size(); //阻塞队列资源个数
        //System.out.println("资源总数:" + resourceCount);
        //创建生产者线程
        Runnable producer=()->{
            while (resourceCount < 1){
                try {
                    int random = new Random().nextInt(100); //生成一个0-100的两位整数
                    queue.put(random);
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "生产了一个整型数据==>"+random + ",当前资源池有"+queue.size()+"个资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }};
        for (int i = 1; i <= 5; i++) { //创建5个生产线程 线程名称从0-4
            new Thread(producer).start();
        }
        //创建消费者线程
        Runnable consumer=()->{
            while (true){
                try {
                    System.out.println("北京时间:" + Instant.now() + ":" + Thread.currentThread().getName() + ":" + "消费了一个整型数据==>"+queue.take() + ",当前资源池有"+queue.size()+"个资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }};
        for (int i = 1; i <= 5; i++) { //创建5个消费线程 线程名称从5-9
            new Thread(consumer).start();
        }
    }
}
Saída do console BlockingQueue

Expandir

  • Implementado através de semáforo
  • Implementado por meio de PipedInputStream/PipedOutputStream

おすすめ

転載: blog.csdn.net/qq_34020761/article/details/132260236