JDK容器与并发—Queue—LinkedBlockingQueue

来源:互联网 发布:泡泡交友软件 编辑:程序博客网 时间:2024/05/21 20:29

概述

      基于单链表的有界或无界阻塞队列。

1)FIFO;

2)可用带容量参数的构造函数设置队列的容量,否则为Integer.MAX_VALUE;

3)在大部分并发场景中,基于链表的队列比基于数组的队列有更高的吞吐量,但性能的预测性更低。

数据结构

      单链表,一把take锁,一个take条件,一把put锁,一个put条件:

// 链表首节点// head.item == nullprivate transient Node<E> head;// 链表尾节点// last.next == nullprivate transient Node<E> last;// takeLockprivate final ReentrantLock takeLock = new ReentrantLock();// 队列为空时,在takeLock上的等待private final Condition notEmpty = takeLock.newCondition();// putLockprivate final ReentrantLock putLock = new ReentrantLock();// 队列已满时,在putLock上的等待private final Condition notFull = putLock.newCondition();static class Node<E> {E item; // 取值有三种情况: // next为后继节点或this(表明后继节点为head.next)或null(表明其为链表尾节点)Node<E> next;Node(E x) { item = x; }}

构造器

// 无参构造// 无界链表:Integer.MAX_VALUEpublic LinkedBlockingQueue() {this(Integer.MAX_VALUE);}// 带容量参数构造public LinkedBlockingQueue(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;last = head = new Node<E>(null);}// 带Collection参数构造,将其中元素入队// 无界链表:Integer.MAX_VALUEpublic LinkedBlockingQueue(Collection<? extends E> c) {this(Integer.MAX_VALUE);final ReentrantLock putLock = this.putLock;putLock.lock(); // 为了可见性加锁,这里不会有竞争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();}}

增删查

基础方法

// notEmpty signal,在put/offer中调用private void signalNotEmpty() {final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {notEmpty.signal();} finally {takeLock.unlock();}}// notFull signal,在take/poll中调用private void signalNotFull() {final ReentrantLock putLock = this.putLock;putLock.lock();try {notFull.signal();} finally {putLock.unlock();}}// 节点入队private void enqueue(Node<E> node) {// assert putLock.isHeldByCurrentThread();// assert last.next == null;last = last.next = node;}// 将链表首节点出队private E dequeue() {// assert takeLock.isHeldByCurrentThread();// assert head.item == null;Node<E> h = head;Node<E> first = h.next;h.next = h; // help GChead = first;E x = first.item;first.item = null;return x;}

      步骤(注意:offer非阻塞版,在获取putLock前先检测队列是否已满,若已满则立即返回false,否则获取putLock):
1)获取putLock;
2)若队列未满,则才将节点入队,成功后若队列还未满,向put/offer线程发送notFull信号;
3)若队列已满,put一直等到队列未满,offer阻塞版则等待timeout时间,超出则立即返回false;
4)释放putLock锁。
5)若入队成功且c==0,则获取takeLock锁,向take/poll线程发送notEmpty信号,再释放takeLock锁。

public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();int c = -1; // 用局部变量标识结果,若返回前为-1,则表明操作失败。put/take线程都采用该方法。Node<E> node = new Node(e);final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;putLock.lockInterruptibly();  // 获取到putLock锁前,可中断的锁的获取  try {while (count.get() == capacity) { // 队列已满,则一直waitnotFull.await();}enqueue(node);  // 将节点入队c = count.getAndIncrement();if (c + 1 < capacity)  // 队列仍未满notFull.signal();  // 向其他put/offer线程发送notFull信号} finally {putLock.unlock();  // 释放putLock}if (c == 0)  // 入队成功signalNotEmpty();  // 获取takeLock锁,向take/poll线程发送notEmpty信号,再释放takeLock锁。}

      与增的过程对立。

      步骤(注意:poll非阻塞版,在获取takeLock前先检测队列是否为空,若为空则立即返回null,否则获取takeLock):
1)获取takeLock;
2)若队列非空,则将链表首节点出队,成功后若队列还未空,向take/poll线程发送notEmpty信号;
3)若队列为空,take一直等到队列未满,poll阻塞版则等待timeout时间,超出则立即返回null;
4)释放takeLock锁。
5)若出队成功且c==capacity,则获取putLock锁,向put/offer线程发送notFull信号,再释放putLock锁。

public E take() throws InterruptedException {E x;int c = -1; // 用局部变量标识结果,若返回前为-1,则表明操作失败。final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();// 获取到takeLock锁前,可中断的锁的获取 try {while (count.get() == 0) {// 队列为空,则一直waitnotEmpty.await();}x = dequeue();// 将链表首节点出队c = count.getAndDecrement();if (c > 1)// 队列仍未满notEmpty.signal();// 向其他take/poll线程发送notEmpty信号} finally {takeLock.unlock();// 释放takeLock}if (c == capacity)// 出队成功signalNotFull();// 获取putLock锁,向put/offer线程发送notFull信号,再释放putLock锁。return x;}

步骤:
1)先检测队列是否为空,若为空则立即返回null;
2)否则获取takeLock;
3)获取链表首节点,若为null,则返回null;否则返回item值;
4)释放takeLock锁。

// 获取链表首元素public E peek() {if (count.get() == 0)return null;final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {Node<E> first = head.next;if (first == null)return null;elsereturn first.item;} finally {takeLock.unlock();}}

迭代器

      弱一致性。

private class Itr implements Iterator<E> {/* * Basic weakly-consistent iterator.  At all times hold the next * item to hand out so that if hasNext() reports true, we will * still have it to return even if lost race with a take etc. */private Node<E> current;private Node<E> lastRet;private E currentElement;Itr() {fullyLock();try {current = head.next;if (current != null)currentElement = current.item;} finally {fullyUnlock();}}public boolean hasNext() {return current != null;}/** * Returns the next live successor of p, or null if no such. * * Unlike other traversal methods, iterators need to handle both: * - dequeued nodes (p.next == p) * - (possibly multiple) interior removed nodes (p.item == null) */private Node<E> nextNode(Node<E> p) {for (;;) {Node<E> s = p.next;if (s == p)return head.next;if (s == null || s.item != null)return s;p = s;}}public E next() {fullyLock();try {if (current == null)throw new NoSuchElementException();E x = currentElement;lastRet = current;current = nextNode(current);currentElement = (current == null) ? null : current.item;return x;} finally {fullyUnlock();}}public void remove() {if (lastRet == null)throw new IllegalStateException();fullyLock();try {Node<E> node = lastRet;lastRet = null;for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) {if (p == node) {unlink(p, trail);break;}}} finally {fullyUnlock();}}}

特性

      实际上put/offer操作与take/poll操作是通过两把锁、两个条件:putLock、notFull、takeLock、notEmpty来进行协作的,来实现LinkedBlockingQueue的功能。

LinkedBlockingQueue的读与写如何保证可见性?

      当节点入队时,需要获取putLock,count增加,后续的读操作可以通过获取putLock锁(通过获取全锁:putLock+takeLock)或获取takeLock锁,读n = count.get(),根据happens-before原则,这样就可以读取到新入队的n个节点。

疑问

      为什么其更高的吞吐量,但性能的预测性更低?

0 0
原创粉丝点击