BlockingQueue之ArrayBlockingQueue
BlockQueue
阻塞队列,基于ReentrantLock设计而来的。
能够保证在单个JVM下,无论并发有多大,都能保证都某一时刻,只有一个线程来进行添加和获取操作。
适用于生产者和消费者模型。
下面以BlockingQueue为列子来进行讲解:
/**
*
* 经典的生产者和消费者案例!
* @author liguang
* @date 2022/7/28 10:53
*/
public class TestOne {
private static final int count = 8;
private static final BlockingQueue<Integer> blockQueue = new ArrayBlockingQueue(8);
static class Provider extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
int i = (int) (Math.random() * 100);
System.out.println(String.format("当前生产者线程%s,生产的值是:%s",Thread.currentThread().getName(),i));
blockQueue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
Integer take = blockQueue.take();
System.out.println(String.format("当前消费者线程%s,消费的值是:%s",Thread.currentThread().getName(),take));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// demo1();
for (int i = 0; i < 10; i++) {
new Consumer().start();
}
for (int i = 0; i < 10; i++) {
new Provider().start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void demo1() {
Provider provider = new Provider();
Consumer consumer = new Consumer();
provider.start();
consumer.start();
try {
provider.join();
consumer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
看下构造方法:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
// 创建的是锁对象。lock做为成员属性
lock = new ReentrantLock(fair);
// 基于锁对象创建两个条件队列
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
看下其中的put和take方法:
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();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 消费者进行消费的时候也需要先获取得到锁
lock.lockInterruptibly();
try {
while (count == 0)
// 如果没有了,阻塞并通知
notEmpty.await();
// 否则就直接出队
return dequeue();
} finally {
// 解锁
lock.unlock();
}
}
重点就是这里的await方法。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 在条件队列中添加一个等待者。这里是一个单链表结构
Node node = addConditionWaiter();
// 然后释放进行锁,等待通知唤醒
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
条件队列:生产者和消费者都待在这一队列里面,单链表结构。
那么这里考虑一种极端情况,如果消费者消费完了,里面没有线程了。那么消费者会通知生产者,让生产者去进行生产:
也就是在await方法中进行操作的。考虑到条件队列中如果有生产者和消费者,那么这种情况下应该如何来进行处理呢?
对应着下面这种情况:
如果说,此时此刻,生产者已经将阻塞队列填满了,那么将会通知条件队列中的线程。而条件队列中的线程会进入到
阻塞队列中,但是发现阻塞队列中还有生产者,那么这个时候需要找到消费者为止。
也就是说,这个时候需要将阻塞队列中的生产者调整到条件队列中,挑选出来对应的消费者。
这个是总体流程结构。
从理论中来,到实践中去,最终回归理论