前言
什么是阻塞队列?有那7种阻塞队列?为什么需要阻塞队列?通过剖析JDK 1.8 源码了解源码设计的精髓和巧妙的地方,并运用在我们实际的项目中,他山之石,可以攻玉。
1. 什么是阻塞队列
感觉并发编程上面的解释不是很容易理解,我们看下源码Doug Lea大佬的注释,意思是阻塞队列支持检索的时候可以等待队列变成非空(也就是阻塞等待), 存储元素的时候,等待空间可用。
BlockingQueue方法有四种形式,具有不同的处理操作的方法,这些操作不能立即得到满足,但可能在将来的某个时候得到满足:
- 抛出一个异常
- 返回一个特殊值(null 或 false,依赖具体的操作)
- 在操作可以成功前,无限期地阻塞当前线程
- 在放弃前只在给定的最大时间限制内阻塞。
2. JDK种7种阻塞队列
JDK 7提供了7个阻塞队列,如下。
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
2.1 ArrayBlockingQueue
ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原 则对元素进行排序。
看下ArrayBlockingQueue 依赖继承关系,底层是以数组Object[] items;
保存元素的。
2.2 LinkedBlockingQueue
LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
2.3 PriorityBlockingQueue
PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序 升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
2.4 DelayQueue
DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队 列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。 只有在延迟期满时才能从队列中提取元素。
使用场景:
- 缓存系统的设计
- 定时任务调度
2.5 SynchronousQueue
SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
2.6 LinkedTransferQueue
LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻 塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
2.6.1 transfer方法
2.6.2 tryTransfer方法
2.7 LinkedBlockingDeque
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。
3. 阻塞队列的实现原理
我们初学线程池可能会有一个疑问,如果队列是空的,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前队列有元素的呢? 如果是你你会怎么去设计?
使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。
以JDK ArrayBlockingQueue 源码分析
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
// 这个是阻塞队列的关键点所在, 定义了两个条件队列
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
我们看下put操作,源码不复杂,我简单解释一下while条件,判断如果队列满了, notFull.await() 等待,当队列不满了,继续执行入队操作。
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();
}
}
take方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞等待在条件notEmpty上
while (count == 0)
notEmpty.await();
// 出队列
return dequeue();
} finally {
lock.unlock();
}
}
最后
对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。
现在竞争这么激烈,只有通过不断学习,提高自己,才能保持竞争力。
对于一些不知道学习什么,没有一个系统路线的程序员,这里给大家提供一些学习资料
需要的小伙伴,可以一键三连,点击这里获取免费领取方式!
《Java核心知识点合集(283页)》
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
《Java中高级核心知识点合集(524页)》
《Java高级架构知识点整理》
《Docker从入门到实践》
《spring could 学习笔记》
《JVM与性能调优知识点整理》
《MySQL性能调优与架构设计解析文档》305页
《Nginx入门到实战》319页
《Java并发编程》385页
《1000道 互联网Java工程师面试题 (485页)》
需要的小伙伴,可以一键三连,点击这里获取免费领取方式!