java多线程-阻塞队列实现消费者生产者

一,Java中三种实现生产者消费者

1,使用wait()/notify()的方式

2,使用J.U.C下Condition的await()/signal()的方式实现

3,使用阻塞队列实现

注:这篇博文主要将使用阻塞队列实现,至于前面的两种可以看看我的另外一篇博客

二,什么是阻塞队列,阻塞队列的特性

1,一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。

2,Java针对阻塞队列的操作方式有不同的处理逻辑

三,阻塞队列的实现原理

1,推荐几篇博客:Java并发讲解

2,对于Java并发的学习,切不可知道几个关键字就可以了,要把J.U.C下的包逐个击破,了解其原理及其使用场景才是学习的关键。

四,阻塞队列有哪些

1,ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列

2,LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

3,PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

4,DelayQueue:一个使用优先级队列实现的无界阻塞队列。

5,SynchronousQueue:一个不存储元素的阻塞队

6,LinkedTransferQueue:一个由链表结构组成的无界阻塞队列

7,LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列

总结,对于无界的队列,值得注意的是,在线程池的使用中,如果请求过多,就会造成OOM.很多队列都作为线程池的等待队列来使用,以满足不同的处理需求,具体看我推荐的博文

五,使用阻塞队列来实现生产消费

1,需求模型如下,该图来自《Java并发编程艺术》

java多线程-阻塞队列实现消费者生产者_第1张图片

2,具体实现并非copy(因为为不全)

3,简单理解:大致分三步

1),生产者生产数据,将数据放入阻塞队列,实际场景,来自客户段的请求之类的

2),消费者1从阻塞队列中拿取数据,当阻塞队列中没有数据时,阻塞队列就会调用LockSupport.park()使调用take()的线程阻塞,那消费者1 就会在那等着,如果遇到异常,该线程会被中断。

3),消费者1 的工作并非是处理这些数据,而是将他们分发给三个阻塞队列,根据得到的数据采用哈希算法,算出这条数据应该去那个队列,那条去那个队列。(不太清楚书上是怎么具体实现的,但是思想都是一样的,使这些数据均匀的分布到三个队列)

4)剩下的消费者就是真正处理数据 的了(这里我简但的做了个打印);

六,代码实现

1,封装数据

package 多线程实战.生产者消费者;

/**
 * 消息数据 待处理的数据
 * 假设保存个人数据
 */
public class MessageData {
    private String name;
    private int id;
    public MessageData(){

    }
    public MessageData(String name,int age){
        this.id = age;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "["+name+"]";
    }
}

2,封装阻塞队列,这里我使用的是ArrayBlockingQueue,也可以采用别的,效率可能会更好

package 多线程实战.生产者消费者;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 *
 */
public class MessageTaskQueue{
    public final  BlockingQueue queue;

    public MessageTaskQueue(int size){
        queue = new ArrayBlockingQueue<>(size);
    }
    public void put(MessageData data){
        try {
            queue.offer(data);
        }catch (Exception e){
            Thread.currentThread().interrupt();
        }
    }
    public MessageData get(){
        try {
             return queue.take();
        }catch (Exception e){
            Thread.currentThread().interrupt();
        }
        return null;
    }
}

3,创建生产者线程,用于生产数据并把数据放入阻塞队列

package 多线程实战.生产者消费者;


public class MessageProducer implements Runnable{
    private MessageTaskQueue queue;
    private MessageData data;
    MessageProducer(MessageTaskQueue queue){
        this.queue = queue;
    }
    @Override
    public void run() {
        for (;;){
            data = new MessageData("LYY-"+System.currentTimeMillis()%9+"["+Thread.currentThread().getName()+"]",1);
            System.out.println("生产消息···"+data.toString());
            queue.put(data);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里的书写可能不太完美

4,创建消息分发的线程,我用一个hashmap保存者阻塞队列id和阻塞队列的关系,

package 多线程实战.生产者消费者;

import java.util.HashMap;

public class DispatchMessageTask implements Runnable{
    private MessageTaskQueue queue;
    private HashMap hashMap;
    public DispatchMessageTask(MessageTaskQueue queue,HashMap hashMap){
        this.hashMap = hashMap;
        this.queue = queue;
    }
    @Override
    public void run() {
        for (;;){
            try {
                MessageData data = queue.get();
                //这里就是所谓的哈希算法,System.nanoTime()这个函数产生的值
                 //就是哈希值,对size取模运算,会得到0,1,2这三个数,他们一一对应一个队列
                int index = (int) (System.nanoTime() % hashMap.size());
                MessageTaskQueue messageTaskQueue = hashMap.get(index);
                System.out.println("消息分发···"+index+" 号队列获得消息: "+data.toString());
                messageTaskQueue.put(data);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

5,创建消费者线程

package 多线程实战.生产者消费者;

public class MessageConsumer implements Runnable{
    private MessageTaskQueue queue;
    MessageConsumer(MessageTaskQueue queue){
        this.queue=queue;
    }
    @Override
    public void run() {
        for (;;){
            MessageData data = queue.get();
            //拿到数据就对数据进行处理
            System.out.println("消费消息···"+data.toString());
        }
    }
}

至于为什么是死循环,因为要一直拿数据,

6,启动类

package 多线程实战.生产者消费者;

import java.util.HashMap;

public class Run {
    public static void main(String[] args) throws Exception{
        //四个阻塞队列
        MessageTaskQueue queue_1 = new MessageTaskQueue(100);
        MessageTaskQueue queue_2 = new MessageTaskQueue(10);
        MessageTaskQueue queue_3 = new MessageTaskQueue(10);
        MessageTaskQueue queue_4 = new MessageTaskQueue(10);
        //用于负载均衡
        HashMap map = new HashMap<>();
        map.put(0,queue_2);
        map.put(1,queue_3);
        map.put(2,queue_4);
        //两个线程生产消息
        MessageProducer producer = new MessageProducer(queue_1);
        Thread producer_Thread_1 = new Thread(producer);
        Thread producer_Thread_2 = new Thread(producer);
        Thread producer_Thread_3 = new Thread(producer);
        //消息分发
        DispatchMessageTask dispatchMessageTask = new DispatchMessageTask(queue_1,map);
        Thread dispach_Thread = new Thread(dispatchMessageTask);
        //消费消息
        MessageConsumer consumer = new MessageConsumer(queue_2);
        MessageConsumer consumer1 = new MessageConsumer(queue_3);
        MessageConsumer consumer2 = new MessageConsumer(queue_4);
        Thread c1_Thread = new Thread(consumer);
        Thread c2_Thread = new Thread(consumer1);
        Thread c3_Thread = new Thread(consumer2);
        //开启线程
        producer_Thread_1.start();
        producer_Thread_2.start();
        producer_Thread_3.start();
        Thread.sleep(1000);
        dispach_Thread.start();
        c1_Thread.start();
        c2_Thread.start();
        c3_Thread.start();
    }
}

用两个线程来生产,数据还是很快被分发消费,自己实现可多加几个,或者把休眠时间调短一点,

运行截图

java多线程-阻塞队列实现消费者生产者_第2张图片

这是一个全过程,可以适当调整阻塞队列的大小,观察队列阻塞的情况。。

参看:《Java并发编程艺术》

你可能感兴趣的:(Java多线程)