LinkedBolckingQueue
LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
原理:
LinkedBlockingQueue底层维持着两把锁,putLock用于put操作添加元素,takeLock用于take操作移除元素,同一时刻只能有一个线程进行put操作,其他想要执行put操作的线程会被阻塞,同时可以有另一个线程执行take操作,其他想要执行take操作的线程也一样会被阻塞;
简单来说,就是put(take)操作都是串行的,同一时刻只能有一个线程去执行put(take)操作,但两个一个执行put操作的线程和一个执行take操作的线程可以同时进行,互不干涉,为了维持线程安全,LinkedBlockingQueue中使用了AtomicInteger类型的变量count来表示队列中的元素个数;
1、继承关系
public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable
2、属性
private final int capacity; //容量,默认情况时则为Integer.MAX_VALUE
private final AtomicInteger count = new AtomicInteger(0); //并发的原子操作类,指当前队列中元素的个数
private transient Node<E> head; //头节点,head.item == null
private transient Node<E> last; //尾结点,last.next == null
//队列同步相关属性,在声明时已经被初始化
private final ReentrantLock takeLock = new ReentrantLock(); //用于take操作的重入锁
private final Condition notEmpty = takeLock.newCondition(); //takeLock的Condition实例
private final ReentrantLock putLock = new ReentrantLock(); //用于put操作的重入锁
private final Condition notFull = putLock.newCondition(); //putLock的Condition实例
看JDK源码可以发现LinkedBlockingQueue内部由链表实现的队列至少有一个节点,头节点不含元素,head.item == null,尾结点last.next == null
;
3、构造函数
- 使用无参构造时容量是int类型的最大值;
- 队列对外表现为空时,也至少包含一个节点(head);
- LinkedBolckingQueue同样不支持添加null元素;
//用默认长度Integer.MAX_VALUE来初始化LinkedBlockingQueue的大小
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//通过capacity来初始化LinkedBlockingQueue的大小
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
//用集合来初始化LinkedBlockingQueue
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null) {
throw new NullPointerException();
}
if (n == capacity) {
throw new IllegalStateException("Queue full");
}
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
4、put() 添加元素
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException(); //待添加元素为null时抛出异常
int c = -1;
Node<E> node = new Node(e); //用待添加元素新建一个节点
final ReentrantLock putLock = this.putLock; //获取putLock锁
final AtomicInteger count = this.count;
//显性的添加可中断锁
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //如果队列已满,将当前线程添加到Condition实例notFull的等待队列中
notFull.await();
}
//队列没满则将新建的节点入队列
enqueue(node);
//调用getAndIncrement()后返回旧值,c为添加节点之前队列中的元素个数count,但count已经进行了原子性的+1操作
c = count.getAndIncrement();
if (c + 1 < capacity) { //c+1 < capacity说明队列未满还可以添加元素,释放等待入队列(put操作)的线程
notFull.signal();
}
} finally {
putLock.unlock(); //显性的释放锁
}
if (c == 0) { //c == 0时,就是添加元素前队列中元素的个数为0,那么添加之后的个数count为1,也就是说队列非空,通知等待出队列(take操作)的线程
signalNotEmpty();
}
}
==================================================================================
private void enqueue(Node<E> node) {
last = last.next = node; //将节点node添加在队尾
}
private void signalNotEmpty() { //通知消费者队列不为空了,
final ReentrantLock takeLock = this.takeLock; //获取takeLock锁
//加锁
takeLock.lock();
try {
//释放一个在notEmpty等待队列中的线程
notEmpty.signal();
} finally {
takeLock.unlock(); //释放锁
}
}
简单来说put()就是:
1)插入元素时
- 若当队列满,就将当前线程加入到notFull的条件等待队列中,
- 当队列不满,则添加元素,
- 若添加之后队列依然未满则通知notFull,
2)队列非空则通知take线程
5、take() 移除元素
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock; //获取takeLock锁
takeLock.lockInterruptibly(); //添加可中断锁
try {
while (count.get() == 0) { //当队列为空时,当前线程加入notFull的条件等待队列中
notEmpty.await();
}
x = dequeue(); //得到队头元素
//调用getAndDecrement()后返回旧值,c为删除节点之前队列中的元素个数count,但此处count已经进行了原子性的-1操作
c = count.getAndDecrement();
if (c > 1) { //c为删除元素之前队列中的元素个数,说明队列中至少还有一个元素,释放notEmpty条件等待队列中的一个线程
notEmpty.signal();
}
} finally {
takeLock.unlock(); //释放锁
}
//删除操作之前c == capacity,说明队列未满,通知等待入队的put线程
if (c == capacity) {
signalNotFull();
}
return x; //返回移除的元素
}
===========================================
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
}
简单来说take()就是:
1)移除元素时
- 当队列为空时,将当前线程加入到notEmpty的条件等待队列中,
- 当队列不为空则移除一个元素,
- 若此时队列依然不为空则通知notEmpty,
2)如果队列非满,就通知put线程