前言
最近在精简系统的部署方案,准备再做一个去RibbitMQ的技术方案,于是研究到了java中的LinkedBlockingQueue。
LinkedBlockingDeque 结构
- LinkedBlockingQueue
- 我们先看下LinkedBlockingDeque类的关系结构
- 是不是和我前面文章写的DelayQueue师出同门
- 还有个很像LinkedBlockingQueue的LinkedBlockingDeque( 双端链表)
- 同属阻塞队列,区别在于 多了一个Deque,Deque是什么?
- Queue:队列,特点,先进后出,只提供追加元素到队列末尾和出队列头元素
- Deque:双端队列,说白了,就是增强了Queue的前后操作,可以前出后出,前入后入
LinkedBlockingQueue
- 单链表实现,只能从head取元素,从tail添加元素,并且都会加锁
- 内部采用可重入锁,保证并发下的线程安全
- LinkedBlockingQueue支持读写分离,读写操作可以并行
- 提供3个构造器:
// 容量默认为 Integer.MAX_VALUE,思考会存在什么问题?
public LinkedBlockingQueue();
// 指定容量
public LinkedBlockingQueue(int capacity)
// 传入一个容器,容量默认也是 Integer.MAX_VALUE
public LinkedBlockingQueue(Collection<? extends E> c)
提供的方法
操作 | 抛出异常 | 非阻塞,返回boolean | 可能阻塞 | 设置超时时间 |
---|---|---|---|---|
入队列 | add(e) | offer(e) | put(e) | offer(e,timeout,unit) |
出队列 | remove() | poll() | take() | poll(timeout, unit) |
查看元素 | element() | peek() | - | - |
- 带异常抛出的,例如队列被元素填满后,再调用add,则会抛出异常IllegalStateException
- 非阻塞,返回Boolean,当元素填完满队列后,再调用offer,插入不成功,不会有异常,而是返回false
- 想实现阻塞功能,调用普通、take,当出现队列拥堵时,会阻塞线程
基于LinkedBlockingQueue实现MQ的思路
- 利用Spring中的事件发布与订阅+异步方法来实现提供发送消息的API
- 事件订阅方收到消息后,将消息put进队列
- 起一个多线程类,实现run方法逻辑,在run中通过死循环对queue进行take()
- 当队列中没有元素时,take()会阻塞着,当有元素放入时,则会取到头元素
- 取到元素后,通过消息对象的属性,反射调用到消息监听者的process()方法即可
- 完善一点:考虑投递或消费失败重试,并且重试多次失败后,考虑持久化到数据库中,便于排查问题或手动重试
可扩展了解
- ConcurrentLinkedQueue,基于链接节点的无界线程安全队列,无锁非阻塞方式
- 使用CAS原子指令来处理对数据的并发访问,是非阻塞算法得以实现的基础
- 具体参看这篇文章:Java并发编程之ConcurrentLinkedQueue详解