阻塞队列BlockingQueue
常见的4中阻塞队列:
1、ArrayBlockingQueue由数组支持的有界队列;
2、LinkedBlockQueue由链接节点至此的可选有界队列
3、PriorityBlockingQueue由优先级堆至此的无界优先级队列;
4、DelayQueue由优先级堆至此的无界优先级队列;
队列的概念:
1、线程安全,由lock锁来保证其互斥性;可以有多个消费者,多个提供者来操作,但是每一次只能由一个来做一个动作,无论是出队还是入队都需要获取这把锁才能进行操作;并且符合队列先进的先出原则;从队列中获取线程如果是提供者那么创建一个新的队列一直去放发现的提供者;
BlockingQueue blockingQueue=new ArrayBlockingQueue(num);
队列的构造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);//创建锁
notEmpty = lock.newCondition();//条件对象
//notEmpty:take时需要等待的条件
notFull = lock.newCondition();//条件对象
//notFull :put的时候需要等待的条件
//为了配合我们的容器的业务逻辑
//比如当我们往容器里面放东西,放东西的时候我们提供者时持有这个锁的,当我们把容器填满了,我们提供者就没有持有锁的必要了
//这时我们的消费者就要去持有这个锁,需要有一个条件去判断提供者是不是要让出这个锁
}
核心方法blockingQueue.put(x)
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;//new一个锁
lock.lockInterruptibly();//这个加锁方法是会抛出中断异常的锁,如果加锁成功就执行下面地代码
try {
while (count == items.length)//判断我们容量和已经添加地量,看看是否放满了
notFull.await();//上面地条件成立执行这个方法,主动让出锁
enqueue(e);//不断地执行这个操作,不停地去入队,知道上面放满了,执行notFull
} finally {
lock.unlock();
}
}
提供者让出线程给消费者使用
public final void await() throws InterruptedException {
if (Thread.interrupted())//查看线程是否被中断过,如果中断过,直接抛异常
throw new InterruptedException();
Node node = addConditionWaiter();//定义条件等待队列
long 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);
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//获取当前锁状态
if (release(savedState)) {//修改当前锁状态,唤醒下个同步队列中的线程
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
构建条件等待队列单向的队列
private Node addConditionWaiter() {
Node t = lastWaiter;//定义队尾节点
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {//判断无效队列
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//构建队列,设置信号量为Node.CONDITION,传入当前线程
/* Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}*/
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
//把前一个几点指向当前节点
lastWaiter = node;//把尾部节点设置成当前节点
return node;
}
blockingQueue.take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//条件和上面不一样
return dequeue();//每次出队的时候会做
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//去通知生产者,把条件等待队列,遍历一遍全部放到同步队列里面
return x;
}
这个方法是队列的构建方法;
条件对象存在的意义在于,提供者和消费者之间要不断的争抢锁,或者说是完成自身操作后,需要把锁让给对方来操作。具体实现如以下方法,我们的put方法是把东西加到队列中。
再比如,我们提供者在操作的时候,如果我们还是只是把操作过提供者的重新放回队列而不与消费者的元素做区分,再重新从队列中获取线程来执行,那么在提供者把队列放满的时候,很可能我们从新再去队列中拿元素还是提供者,这明显不合理。所以我们把消费者和提供者分成两种队列存储。当提供者把队列填满了,直接把锁让给消费者,当消费者执行完毕后又把锁交给提供者。这种队列叫做条件等待队列。
线程获取锁的条件是:只有在CLH 队列等待的node节点,并且是node节点的前驱节点是sinal(-1)
条件队列里的线程是不可以获取锁的!