1.阻塞队列简介
阻塞队列是支持两个附加操作的队列(阻塞插入和阻塞移除)
- 阻塞插入:队列满时会阻塞插入元素的线程,直到队列不满
- 阻塞移除:队列为空时,获取元素的线程会等待元素变为非空
阻塞队列常用于生产者和消费者的场景
阻塞队列不可用(阻塞)时插入和移除操作的四种处理方式:
- 抛出异常:队列满再插入,队列空移除都会抛出异常
- 返回特殊值:插入成功返回true,移除成功返回元素,否则为null
- 一直阻塞:会等待队列不满或不空
- 超时退出:线程等待一段时间后退出
无界队列不会出现队列满的情况
2.Java中的阻塞队列
2.1 ArrayBlockingQueue
由数组实现的有界阻塞队列,按照FIFO原则对元素进行排序
默认情况下不是公平的(访问顺序与阻塞先后顺序无关)
如下可创建一个公平的阻塞队列:
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
内部是通过可重入锁ReentrantLock实现公平性的
2.2 LinkedBlockingQueue
用链表实现的有界阻塞队列,默认和最大长度都是Integer.MAX_VALUE
2.3 PriorityBlockingQueue
支持优先级的无界阻塞队列,默认采用自然升序,不保证同优先级元素顺序
自定义排序规则:
- 可以实现元素compareTo()方法指定元素排序顺序
- 也可以指定构造方法的参数Comparator指定排序规则
2.4 DelayQueue
支持延时获取元素的无界阻塞队列,使用PriorityQueue实现,队列中元素必须实现Delayed接口,创建元素时可以指定延时时长,只有延时时间满才能获取元素
使用场景:
- 缓存失效:用DelayQueue保存缓存元素有效期
- 定时任务调度:用DelayQueue保存当天要执行的任务和执行时间
如何实现Delayed接口
- 创建对象,初始化基本数据
- 实现getDelay方法,返回还需要延时多长时间
- 实现compareTo方法用来指定元素顺序
实现延时阻塞队列
从队列中获取元素时,如果元素没有达到延时时间,则拒绝
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
return q.poll();
else if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//等待delay纳秒
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
2.5 SynchronousQueue
不存储元素的阻塞队列,适合传递性场景。
线程每一个put()操作必须等待一个take()操作,否则不能继续添加元素;多个线程的put()或take()操作会在等待队列中等待,支持公平访问,默认情况下是非公平的
put等待:(公平锁)
take等待:
2.6 LinkedTransferQueue
由一个链表结构组成的无界阻塞TranserQueue队列,相对于其他阻塞队列,多了tryTransfer和transfer方法
transfer方法
可以把生产者传入的元素立刻传输(transfer)给消费者,如果没有消费者在等待接收,就会将元素存放在队列的tail节点,等到元素被消费者接收了才返回
tryTransfer方法
试探能否直接将生产者传入的元素传给消费者,无论是否接收,都立即返回结果
2.7 LinkedBlockingDeque
由链表构成的双向阻塞队列,可以从队列的两端插入和移除元素
初始化时可以设置容量防止其过度膨胀
双向队列可用在"工作窃取"模式中
3.阻塞队列实现原理
阻塞队列主要是使用通知模式实现,以ArrayBlockingQueue为例
ArrayBlockingQueue—>Condition.await—>LockSupport.park(this)—>unsafe.park()实现
unsafe.park是个native方法:不同操作系统上有不同实现
- Linux:使用系统方法
pthread_cond_wait
实现park- unpark方法使用
pthread_cond_signal
实现
- unpark方法使用
- Windows:使用 WaitForSignalObject 实现(Windows API)