Bootstrap

使用阻塞队列实现生产者消费者模式

一、 背景

在做大批量数据的导入操作时,为了提升性能,我们是否可以使用阻塞队列实现异步读取数据和处理数据呢?显然是可以这样操作的,我们可以使用主线程读取Excel数据,将数据放入阻塞队列中,同时新建一个子线程,对读取的Excel数据进行系列的逻辑处理,这样等于本来是一个人做的事,我们改成了两个人同时进行。再进一步思考,既然是异步生产和消费数据,我们是否可以使用多线程呢,把拆分成两个人改成拆分成10个人,5个人生产数据,5个人读取数据,这样是不是性能再次提升呢?接下来,我就使用阻塞队列来实现生产者和消费者模式。

二、代码实现

2.1 定义生产者

数据传输对象QueueDataDTO:

package concurrents.blockqueue.dto;

import lombok.Data;

/**
 * @author hjf
 * @date 2022-06-27 15:17
 *
 * 生产者、消费者生产消费的数据
 */
@Data
public class QueueDataDTO {

    private String name;

}

生产者Producer:

package concurrents.blockqueue.producer;

import concurrents.blockqueue.dto.QueueDataDTO;
import lombok.SneakyThrows;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author hjf
 * @date 2022-06-27 15:23
 * 
 * 生产者
 */
public class Producer implements Runnable{

    private final ArrayBlockingQueue<QueueDataDTO> dataQueue;

    public Producer(ArrayBlockingQueue<QueueDataDTO> dataQueue) {
        this.dataQueue = dataQueue;
    }

    @SneakyThrows
    @Override
    public void run() {

        int count = 1;
        while (count < 10){
            QueueDataDTO dataDTO = new QueueDataDTO();
            dataDTO.setName("第"+count+"数据");
            count++;
            Thread.sleep(400);
            System.out.println("生产第"+count+"条数据");
            dataQueue.add(dataDTO);
        }

    }
}

2.2 定义消费者

消费者Consumer:

package concurrents.blockqueue.consumer;

import concurrents.blockqueue.dto.QueueDataDTO;
import lombok.SneakyThrows;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author hjf
 * @date 2022-06-27 15:12
 *
 * 消费者
 */
public class Consumer implements Runnable{

    private final ArrayBlockingQueue<QueueDataDTO> dataQueue;

    public Consumer(ArrayBlockingQueue dataQueue) {
        this.dataQueue = dataQueue;
    }

    @SneakyThrows
    @Override
    public void run() {
        QueueDataDTO data;
        //poll从阻塞队列取出一个数据,限制取数时长为2秒,
        // 这里做的目的限制阻塞时间最长2秒来结束子线程操作
        while ((data = dataQueue.poll(2, TimeUnit.SECONDS)) != null){
            System.out.println("消费者消费数据,数据为:"+ data.toString());
        }
    }
}

2.3 异步生产消费消息

主函数ConsumerAndProduceDemo示例:

package concurrents.blockqueue;

import concurrents.blockqueue.consumer.Consumer;
import concurrents.blockqueue.dto.QueueDataDTO;
import concurrents.blockqueue.producer.Producer;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author hjf
 * @date 2022-06-27 14:40
 *
 * 阻塞队列实现生产者、消费者
 * 生产者、消费者模式属于异步,发送方不需要知道接受方是否接受到消息,其只负责发送
 * dubbo接口,属于同步消息,请求方需要根据发送方结果做一定的逻辑处理
 */
public class ConsumerAndProduceDemo {

    private static ArrayBlockingQueue<QueueDataDTO> dataQueue = new ArrayBlockingQueue<QueueDataDTO>(1);

    public static void main(String[] args) {

        Consumer consumer = new Consumer(dataQueue);
        Thread thread = new Thread(consumer);
        thread.start();
        Producer producer = new Producer(dataQueue);
        producer.run();
    }

}

2.4 结果展示
生产第1条数据
消费者消费数据,数据为:QueueDataDTO(name=第1数据)
生产第2条数据
消费者消费数据,数据为:QueueDataDTO(name=第2数据)
生产第3条数据
消费者消费数据,数据为:QueueDataDTO(name=第3数据)
生产第4条数据
消费者消费数据,数据为:QueueDataDTO(name=第4数据)
生产第5条数据
消费者消费数据,数据为:QueueDataDTO(name=第5数据)
生产第6条数据
消费者消费数据,数据为:QueueDataDTO(name=第6数据)
生产第7条数据
消费者消费数据,数据为:QueueDataDTO(name=第7数据)
生产第8条数据
消费者消费数据,数据为:QueueDataDTO(name=第8数据)
生产第9条数据
消费者消费数据,数据为:QueueDataDTO(name=第9数据)

三、总结

demo我们定义阻塞队列的长度为1,展示的是通过主线程来生产数据,同时新建一个子线程来消费数据,从打印的结果中也能看出,数据是顺序生产、顺序消费的,如果队列中数据达到定义的队列长度,生产者是阻塞的,同样,消费者消费数据,如果队列为空其也是阻塞的。

我们在日常开发中,如果数据量很大,性能要求高,我们可以改造成多线程异步生产消费,生产者和消费者都定义多个线程操作,同时扩大阻塞队列的长度,将长度扩大至我们定义的线程数量的3/4左右即可。

;