什么是阻塞队列
阻塞队列:从名字可以看出,他也是队列的一种,那么他肯定是一个先进先出(FIFO)的数据结构。与普通队列不同的是,他支持两个附加操作,即阻塞添加和阻塞删除方法。
阻塞队列的特点
阻塞添加:当阻塞队列是满时,往队列里添加元素的操作将被阻塞。
阻塞移除:当阻塞队列是空时,从队列中获取元素/删除元素的操作将被阻塞。
在多线程中,阻塞的意思是,在某些情况下会挂起线程,一旦条件成熟,被阻塞的线程就会被自动唤醒。
阻塞队列不用手动控制什么时候该被阻塞,什么时候该被唤醒,简化了操作。
根据插入和取出两种类型的操作,具体分为下面一些类型:
抛出异常 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
特殊值 插入方法,成功返回true 失败返回false
移除方法,成功返回元素,队列里面没有就返回null
一直阻塞 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
超时退出 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出
BlockingQueue的实现类
ArrayBlockingQueue 由数组构成的有界阻塞队列
LinkedBlockingQueue 由链表构成的有界阻塞队列
PriorityBlockingQueue 支持优先级排序的无界阻塞队列
DelayQueue 支持优先级的延迟无界阻塞队列
SynchronousQueue 单个元素的阻塞队列
LinkedTransferQueue 由链表构成的无界阻塞队列
LinkedBlockingDeque 由链表构成的双向阻塞队列
SynchronousQueue: 队列只有一个元素,如果想插入多个,必须等队列元素取出后,才能插入,只能有一个“坑位”,用一个插一个。
需要注意的是LinkedBlockingQueue虽然是有界的,但有个巨坑,其默认大小是Integer.MAX_VALUE,高达21亿,一般情况下内存早爆了(在线程池的ThreadPoolExecutor有体现)。
SynchronusQueue
他是不存储元素的,来一个,消费一个。(同一时间内只能添加一个元素)
ArrayListBlockingQueue原理
通过源码来看,其实ArrayListBlockingQueue是通过ReentrantLock和Condition条件队列来实现阻塞的。一些成员变量如下:
//存储数据
final Object[] items;
//返回获取数据的索引,主要用于take、poll、peek、remove方法
int takeIndex;
//返回添加数据的索引,主要用于 put、offer、add 方法
int putIndex;
// 队列元素的个数
int count;
//可重入锁
final ReentrantLock lock;
//条件对象,用于通知take方法队列的线程
private final Condition notEmpty;
//条件对象,用于通知put方法队列的线程
private final Condition notFull;
//迭代器
transient Itrs itrs = null;
阻塞队列的应用场景
线程池的底层存储;
生产消费队列模式。
拓展
LinkedBlockingQueue和ArrayBlockingQueue区别
队列大小不同;
arrayBlockingQueue在初始化的时候,必须指定队列的大小;
而LinkedBlockingQueue在初始化的时候,如果你没有指定大小,则会默认Integer.MAX_VALUE,是一个很大的值,这样就会导致数据在一个不可控范围,一旦添加速度远大于移除的速度时,可能会有内存泄漏的风险。
底层实现不同;
arrayBlockingQueue的底层是一个数组,而LinkedBlockingQueue底层是一个链表结构。官方文档介绍中,LinkedBlockingQueue的吞吐行是高于arrayBlockingQueue;但是在添加或移除元素中,LinkedBlockingQueue则会生成一个额外的Node对象,对GC可能存在影响;
至于为什么说LinkedBlockingQueue的吞吐性是高于arrayBlockingQueue:
吞吐性能强是因为有两个锁,试想一下,Array里面使用的是一个锁,不管put还是take行为,都可能被这个锁卡住,而Linked里面put和take是两个锁,put只会被put行为卡住,而不会被take卡住,因此吞吐性能自然强于Array。 而“less predictable performance”这个也是显而易见的,Array采用的时固定内存,而Linked采用的时动态内存,无论是分配内存还是释放内存(甚至GC)动态内存的性能自然都会比固定内存要差。
锁机制不一样;
arrayBlockingQueue使用的一个锁来控制,LinkedBlockingQueue使用了2个锁来控制,一个名为putLock,另一个是takeLock,但是锁的本质都是ReentrantLock。
传统模式
要求:初始值为0的变量,两个线程交替操作,一个+1,一个-1,执行五轮。
//资源类
class MyResource{
int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//自增
public void increaseNum(){
lock.lock();
try{
//判断
while(number != 0){
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//唤醒
condition.signalAll();
}catch(Exception e){
e.getStackTrace();
}finally {
lock.unlock();
}
}
//自减
public void decrNum(){
lock.lock();
try{
//判断
while(number == 0){
condition.await();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//唤醒
condition.signalAll();
}catch(Exception e){
e.getStackTrace();
}finally {
lock.unlock();
}
}
}
public class ProdConsTradTest {
public static void main(String[] args) {
MyResource myResource = new MyResource();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
myResource.increaseNum();
}
},"Prod").start();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
myResource.decrNum();
}
},"Cons").start();
}
}
阻塞队列模式
class MyData {
//全局开关
private volatile boolean flag = true;
private BlockingQueue<String> queue;
private AtomicInteger atomicInteger = new AtomicInteger();
public MyData(BlockingQueue<String> queue) {
this.queue = queue;
}
public void myProd() throws InterruptedException {
String data = null;
boolean isOfferSuccess;
while(flag){
data = atomicInteger.incrementAndGet()+"";
isOfferSuccess = queue.offer(data, 2l, TimeUnit.SECONDS);
if(isOfferSuccess){
System.out.println(Thread.currentThread().getName()+"线程\t 插入队列成功 \t 插入队列的值为:"+data);
}else{
System.out.println(Thread.currentThread().getName()+"线程\t 插入队列失败");
}
TimeUnit.SECONDS.sleep(1);
}
}
public void myCons() throws InterruptedException {
String result =null;
while(flag){
result = queue.poll(2l, TimeUnit.SECONDS);
if(Objects.isNull(result) || result.equalsIgnoreCase("")){
flag = false;
System.err.println(Thread.currentThread().getName() + "\t超过2秒钟没有消费,退出消费");
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列\t消费\t" + result + "\t成功");
}
}
public void stop() {
this.flag = false;
}
}
public class ProdConsBlockingQueueTest {
public static void main(String[] args) {
MyData myData = new MyData(new ArrayBlockingQueue<>(10));
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+" \t 生产者线程开始生产");
try {
myData.myProd();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Pord").start();
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+" \t 消费者线程开始消费");
try {
myData.myCons();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Cons").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("5秒钟后,叫停");
myData.stop();
}
}