初识队列
队列大家都知道,大致如下图所示,那么为什么叫阻塞队列呢
举个例子,有一家商店,从工厂进货,顾客从商店买货,那么工厂就是生产者,顾客就是消费者
如果,商店没货了,顾客就需要在商店等着,这就阻塞住了
另一种情况,如果工厂生产的东西太多了,商店放不下了,工厂就得等商店的东西卖一卖再放,这也就会阻塞住
阻塞其实就是队列的一个特性
java中Queue家族
从下图可以看出,java中队列家族有这么庞大,我们看介绍按需选用就行了
ArrayBlockingQueue
这个队列被成为有界队列,那么为什么被称为有界队列呢,因为它的队列中必须传入一个capacity,也就是队列的容量
并且如果消费不及时,造成队列满的话,会被阻塞,
如果用错api的话,会抛出异常,或者入队不成功,造成消息丢失
条件锁
- 如果在商店满了的情况下,商店会通知工厂,队列满了,先不要送货了
- 在商店空了的情况下,会通知顾客,先等会,然后通知工厂,可以送货了
- 而一旦商店有货了,就会通知顾客来购买
以上就是条件锁的逻辑,但是在队列里面要简单一点
消费者消费不到消息了,就会阻塞等待,一旦生产者有消息了,就会通知消费者消费
一旦队列中满了,生产者会阻塞等待,一旦队列中有坑儿时,就会通知生产者进行入队
条件锁的实现
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject
条件锁的实现为上边这个类,也是AQS下的
条件锁其实是AQS提供的一个特殊锁,允许用户在任意地方阻塞,在任意地方解阻塞
用户只需根据自己的条件去判断使用,所以它才叫条件锁
ArrayBlockingQueue解析
我们从API追溯源码来看这个队列的实现
构造方法
//capacity 队列初始容量
public ArrayBlockingQueue(int capacity)
//fair为队列中的锁的种类,公平锁或者非公平锁
public ArrayBlockingQueue(int capacity, boolean fair)
//collection为队列初始值,但是不能为空,否则会抛异常
public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c)
入队
阻塞方法**put(E e)**的实现
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//此处是对inerrupt信号的校验,如果程序调用了Thread.interrept()方法,就会抛异常
//否则,会正常加锁
lock.lockInterruptibly();
try {
while (count == items.length)
//notFull为条件锁,如果队列中放满了,则会阻塞等待,如果消费者取了一条数据了,才会被唤醒
notFull.await();
//入队操作
enqueue(e);
} finally {
lock.unlock();
}
}
出队/消费
public E take() throws InterruptedException {
//此处是对inerrupt信号的校验,如果程序调用了Thread.interrept()方法,就会抛异常
//否则,会正常加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
//notEmpty为条件锁,如果队列中位空时,会触发阻塞等待,等到队列中有值时,才会被解阻塞
notEmpty.await();
//出队操作
return dequeue();
} finally {
lock.unlock();
}
}
图解
正常队列
消费不及时
消费不及时会导致队列满了,就会进行阻塞
消费者每消费一个消息,就会发送notFull信号,去唤醒生产者
生产不及时
生产不及时会导致消息队列为空,消费者就会阻塞
每当生产者放一个消息时,就会发送notEmpty信号解阻塞消费者