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个节点。
疑问
为什么其更高的吞吐量,但性能的预测性更低?
- JDK容器与并发—Queue—LinkedBlockingQueue
- JDK容器与并发—Queue
- JDK容器与并发—Queue—Interface
- JDK容器与并发—Queue—ArrayBlockingQueue
- JDK容器与并发—Queue—PriorityQueue
- JDK容器与并发—Queue—PriorityBlockingQueue
- JDK容器与并发—Queue—DelayQueue
- JDK容器与并发—Queue—ConcurrentLinkedQueue
- JDK容器与并发—Queue—LinkedTransferQueue
- JDK容器与并发—Queue—SynchronousQueue
- JDK容器与并发—并发
- JDK容器与并发—JDK容器框架
- JDK容器与并发—数据结构
- JDK容器与并发—List
- JDK容器与并发—Map
- JDK并发工具类源码学习系列——LinkedBlockingQueue
- JDK容器与并发—List—ArrayList
- JDK容器与并发—List—LinkedList
- 报道贴
- 系统镜像盘ISO的制作--基于tinycorelinux
- 三、命令模式Commond(行为型模式)
- MTK Nucleus平台软件log抓取方法
- 文本文件中查找第一字段包含786707,第二字段是11的所有行(命令行操作)
- JDK容器与并发—Queue—LinkedBlockingQueue
- java反射代理学习
- Hibernate —— 原生SQL的实例
- php伪装客户IP采集
- 多进程并发编程----进程间传递文件描述符案例
- jasperreport position type 详解
- 如何将pdf转换成ppt演讲稿
- easy touch利用playmaker拖动ngui对象
- iOS APP上架详细流程