LinkedBlockingQueue
LinkedBlockingQueue继承体系
与ArrayBlockingQueue一样,LinkedBlockingQueue实现了Queue、BlockingQueue接口,继承AbstractCollection类。
源码分析
LinkedBlockingQueue的成员变量
// 队列的容量,若不指定,默认 Integer.MAX_VALUE private final int capacity; // 队列中元素的数量 private final AtomicInteger count = new AtomicInteger(); // 队列头节点 transient Node<E> head; // 队列尾节点 private transient Node<E> last; // 读锁 take() 等获取元素用到的可重入排他锁 private final ReentrantLock takeLock = new ReentrantLock(); // 阻塞获取数据,用于线程挂起、线程唤醒 private final Condition notEmpty = takeLock.newCondition(); // put() 等添加元素用到的可重入排它锁 private final ReentrantLock putLock = new ReentrantLock(); // 写锁 阻塞添加数据,用于线程挂起、线程唤醒 private final Condition notFull = putLock.newCondition();
LinkedBlockingQueue的链表节点静态类:
static class Node<E> { // 元素 E item; // 指向下一节点的Node Node<E> next; // 构造函数 Node(E x) { item = x; } }
LinkedBlockingQueue基于Node节点实现元素的存储,通过next属性将添加的元素构成一个单向链表,在成员变量中声明了链表的头节点head、链表的尾节点last。
添加元素
// 元素添加进队列 private void enqueue(Node<E> node) { // 将node添加进队列 - 单向链表的尾部 last = last.next = node; }
offer(e) - 添加元素
// 添加元素至队列尾 public boolean offer(E e) { // 添加元素为null,抛出异常 if (e == null) throw new NullPointerException(); // 获取当前队列中元素总数 final AtomicInteger count = this.count; // 队列已满,添加元素失败,返回1false if (count.get() == capacity) return false; // 添加元素e前,队列中的元素总数 int c = -1; // 创建Node节点,封装添加的e元素 Node<E> node = new Node<E>(e); // 获取锁对象 final ReentrantLock putLock = this.putLock; // 加锁 putLock.lock(); try { // 队列元素总数 小于 队列容量 if (count.get() < capacity) { // 元素添加进队列中 enqueue(node); // 队列元素总数 + 1 c = count.getAndIncrement(); // 当前元素添加进队列后,队列未满 if (c + 1 < capacity) // 唤醒其他添加元素线程 notFull.signal(); } } finally { // 释放锁 putLock.unlock(); } // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0 // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程 if (c == 0) signalNotEmpty(); // 返回添加结果 return c >= 0; }
offer(e)在队列尾部添加元素,若队列中的数组元素已满,返回false;否则将元素添加进队列中,返回false。
若添加元素后队列未满,唤醒其他添加元素的线程(put);
若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。
与ArrayBlockingQueue基于数组添加不同的是,LinkedBlockingQueue基于链表添加元素。
offer(e, timeout, unit)
LinkedBlockingQueue#offer(e, timeout, unit) 详情如下:
// 添加元素至队列尾 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { // 添加元素为null,抛出异常 if (e == null) throw new NullPointerException(); // 阻塞时间转化为纳秒 long nanos = unit.toNanos(timeout); // 添加元素e前,队列中的元素总数 int c = -1; // 获取锁对象 final ReentrantLock putLock = this.putLock; // 获取当前队列中元素总数 final AtomicInteger count = this.count; // 加锁 putLock.lockInterruptibly(); try { // 队列已满 while (count.get() == capacity) { // 阻塞时间已达到 if (nanos <= 0) // 返回false return false; // 阻塞时间未达到,继续阻塞,并返回阻塞的剩余时间 nanos = notFull.awaitNanos(nanos); } // 添加进队列中 enqueue(new Node<E>(e)); // 获取添加元素前的总数 c 并获取添加元素后的总数 count + 1 c = count.getAndIncrement(); // 当前元素添加进队列后,队列未满 if (c + 1 < capacity) // 唤醒其他添加元素线程 notFull.signal(); } finally { // 释放锁资源 putLock.unlock(); } // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0 // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程 if (c == 0) signalNotEmpty(); // 添加成功返回false return true; }
offer(e, timeout, unit)在队列尾部添加元素,若队列中的数组元素已满,阻塞timeout时间,若阻塞时间已达到,仍未添加成功,返回false。
阻塞时间未到达添加成功,若添加元素后队列未满,唤醒其他添加元素的线程(put);
若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。
put(e) - 添加元素
LinkedBlockingQueue#put(e) 详情如下:
// 添加元素至队列尾 public void put(E e) throws InterruptedException { // 添加元素为null,抛出异常 if (e == null) throw new NullPointerException(); // 添加元素e前,队列中的元素总数 int c = -1; // 创建Node节点,封装添加的e元素 Node<E> node = new Node<E>(e); // 获取锁对象 final ReentrantLock putLock = this.putLock; // 获取当前队列中元素总数 final AtomicInteger count = this.count; // 加锁 putLock.lockInterruptibly(); try { // 当前队列已满,put操作挂起线程 while (count.get() == capacity) { notFull.await(); } // 元素添加进队列 enqueue(node); // 获取添加元素前的总数 c 并获取添加元素后的总数 count + 1 c = count.getAndIncrement(); // 添加元素后,队列未满,唤醒put添加元素线程操作 if (c + 1 < capacity) notFull.signal(); } finally { // 释放锁资源 putLock.unlock(); } // c = count.getAndIncrement() => 先获取队列中的元素总数,若之前队列为空,即count = 0 // 添加元素后,队列元素总数为1,此时需要唤醒take阻塞获取元素的线程 if (c == 0) signalNotEmpty(); }
put(e)阻塞添加元素至队列尾部,若当前队列已满,当前put线程挂起。当获取元素线程从队列中拿走元素,队列中有可取空间时,唤醒挂起的put线程,将元素添加进队列中。
若添加元素后队列未满,唤醒其他添加元素的线程(put);
若添加元素前,队列为空,在元素添加成功后,唤醒获取元素的线程(take)。
总结
方法 |
不同点 |
offer(e) |
队列容量已满,添加失败,直接返回false |
offer(e,timeout,unit) |
队列容量已满,阻塞等待timeout时间,若阻塞时间到达,仍添加失败,直接返回false |
put(e) |
队列容量已满,阻塞等待直到队列中有可添加的空间 |
出队元素
LinkedBlockingQueue#dequeue() 详情如下:
// 从队列头元素中移除并返回元素 private E dequeue() { // 获取队列头节点 Node<E> h = head; // 获取队列头节点的next Node<E> first = h.next; // 将当前队列头节点的next作为新的头节点,原头节点通过GC回收 h.next = h; head = first; // 获取并记录新头节点的元素值 E x = first.item; // 当前节点的元素值设置为null first.item = null; // 返回当前节点的元素值 return x; }
peek() - 获取元素
LinkedBlockingQueue#peek() 详情如下
// 获取队列头节点 public E peek() { // 队列中无可取的元素 if (count.get() == 0) // 返回null return null; // 获取锁对象 final ReentrantLock takeLock = this.takeLock; // 加锁 takeLock.lock(); try { // 获取队列元素头节点 Node<E> first = head.next; // 头节点为null,返回null if (first == null) return null; // 头节点不为null,返回节点中的元素值 else return first.item; } finally { // 释放锁 takeLock.unlock(); } }
peek()获取队列中的头元素,若队列中无可取的元素,返回null;获取head的next节点,若该节点为null,则返回null,否则返回节点中的item值。
poll() - 获取元素
LinkedBlockingQueue#poll() 详情如下:
// 获取队列头节点 public E poll() { // 获取队列中的元素总数 final AtomicInteger count = this.count; // 队列中无可取的元素,返回null if (count.get() == 0) return null; // 返回元素变量 E x = null; // 本次获取元素操作前,队列中元素的总数 int c = -1; // 获取 take 的锁对象 final ReentrantLock takeLock = this.takeLock; // 加锁 takeLock.lock(); try { // 队列中有可取元素 if (count.get() > 0) { // 获取队列头元素 x = dequeue(); // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1 c = count.getAndDecrement(); // 本次获取元素前,队列非空,至少有两个可取元素 if (c > 1) // 唤醒其他获取队列元素的线程 notEmpty.signal(); } } finally { // 释放锁资源 takeLock.unlock(); } // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1, // 队列中有可用空间,唤醒put线程添加元素 if (c == capacity) signalNotFull(); // 返回元素值 return x; }
poll()获取队列中的头元素,若队列中无可取的元素,返回null;若队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程,若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程
添加元素。
poll(timeout, unit)
LinkedBlockingQueue#poll(timeout, unit) 详情如下:
// 获取队列头节点(阻塞指定时间) public E poll(long timeout, TimeUnit unit) throws InterruptedException { // 返回元素变量 E x = null; // 本次获取元素操作前,队列中元素的总数 int c = -1; // 转换为纳秒 long nanos = unit.toNanos(timeout); // 当前队列中元素总数 final AtomicInteger count = this.count; // 获取锁对象 final ReentrantLock takeLock = this.takeLock; // 加锁 takeLock.lockInterruptibly(); try { // 当前队列可取元素为空 while (count.get() == 0) { // 阻塞时间已达到 if (nanos <= 0) // 返回null return null; // 阻塞时间未达到,继续阻塞,并返回剩余阻塞时间 nanos = notEmpty.awaitNanos(nanos); } // 获取队列头元素 x = dequeue(); // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1 c = count.getAndDecrement(); // 本次获取元素前,队列非空,至少有两个可取元素 if (c > 1) // 唤醒其他获取队列元素的线程 notEmpty.signal(); } finally { // 释放锁资源 takeLock.unlock(); } // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1, // 队列中有可用空间,唤醒put线程添加元素 if (c == capacity) signalNotFull(); // 返回元素值 return x; }
poll(timeout, unit)获取队列中的头元素,若队列中无可取的元素,阻塞timeout时间,阻塞时间已达到仍未获取到头元素,返回null;若获取到队列头元素,并且队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程(poll、take),若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程添加元素。
take() - 阻塞获取元素
LinkedBlockingQueue#take() 详情如下:
// 获取队列头节点(阻塞) public E take() throws InterruptedException { // 返回元素变量 E x; // 本次获取元素操作前,队列中元素的总数 int c = -1; // 当前队列中元素总数 final AtomicInteger count = this.count; // 获取锁对象 final ReentrantLock takeLock = this.takeLock; // 加锁 takeLock.lockInterruptibly(); try { // 当前队列可取元素为空,挂起线程,待put、offer添加元素成功后,唤醒线程 while (count.get() == 0) { notEmpty.await(); } // 获取队列头元素 x = dequeue(); // 得到本次获取元素前的总数 c 并对获取元素后的总数 count - 1 c = count.getAndDecrement(); // 本次获取元素前,队列非空,至少有两个可取元素 if (c > 1) // 唤醒其他获取队列元素的线程 notEmpty.signal(); } finally { // 释放锁 takeLock.unlock(); } // 本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1, // 队列中有可用空间,唤醒put线程添加元素 if (c == capacity) signalNotFull(); // 返回元素值 return x; }
take()获取队列中的头元素,若队列中无可取的元素,挂起线程,等到put/offer添加元素成功后唤醒put线程;获取队列头元素,若队列中有多个可取元素,获取队列头节点并唤醒其他获取元素线程,若在本次获取元素前,队列已满,本次获取元素后,当前count = capacity - 1,队列中有可用空间,唤醒put线程添加元素。
总结
方法
|
不同点
|
peek()
|
队列无可取元素,获取失败,直接返回null
|
poll()
|
队列无可取元素,直接返回null
|
poll(timeout,unit)
|
队列无可取元素,阻塞等待timeout时间,若阻塞时间到达,仍获取失败,直接返回null
|
take()
|
队列无可取元素,阻塞等待直到队列中有可取元素
|
总结
LinkedBlockingQueue基于单向链表实现元素存取,链表中的节点用Node表示,Node中包含当前元素值及指向下一节点的指向next。LinkedBlockingQueue内部维护Atomic原子类count成员变量,记录当前数组中的元素数量,维护当前队列头节点head、尾节点last。LinkedBlockingQueue内部提供了读锁和写锁,读写不互斥。
LinkedBlockingQueue数据结构详情如下:
添加元素时,优先判断count值是否等于队列容量,即队列是否满了,若队列满了,调用不同的添加方法有不同的结果。若在添加元素前队列为空,在添加元素后,唤醒(take)获取元素线程;
获取元素时,优先判断count值是否等于0,即队列是否为空,对队列为空,直接返回null;poll/take在获取元素前,队列中有多个元素,唤醒(take)获取线程,若在获取队列前队列已满,在获取元素后,队列中有可用空间,唤醒(put)添加元素线程。
LinkedBlockingQueue阻塞添加、获取队列元素是基于AQS中的ConditionObject实现的。