Java常见集合框架(十五):Queue之ArrayBlockingQueue、LinkedBlockingQueue
来源:互联网 发布:数据交易平台 源码 编辑:程序博客网 时间:2024/05/02 04:41
ArrayBlockingQueue
public class ArrayBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable
- 一个由数组支持的有界阻塞队列。
- 队列按 FIFO(先进先出)原则对元素进行排序。
- 试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
- 支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
成员变量
/** 队列元素存储在此数组中 */ private final E[] items; /** 下一个被take or poll的元素的index */ private int takeIndex; /** 下一个被put, offer, or add的元素的index */ private int putIndex; /** 队列中元素数 */ private int count; /** 可重入的互斥锁 Lock */ private final ReentrantLock lock; /** 标识队列是否为空,即获取元素的等待条件 */ private final Condition notEmpty; /** 标识队列是否已满 ,即放入元素的等待条件*/ private final Condition notFull;
构造方法
/** * 创建一个带有给定的(固定)容量和默认访问策略的ArrayBlockingQueue。 */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * 创建一个具有给定的(固定)容量和指定访问策略的ArrayBlockingQueue。 */ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = (E[]) new Object[capacity];//实例化指定容量数组 lock = new ReentrantLock(fair);//实例化指定策略的重入锁(公平或非公平) //实例化判断是否非空的 Condition 实例 notEmpty = lock.newCondition(); //实例化判断是否已满的 Condition 实例 notFull = lock.newCondition(); } /** * 创建一个具有给定的(固定)容量和指定访问策略的ArrayBlockingQueue。 * 它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。 */ public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { this(capacity, fair); if (capacity < c.size()) throw new IllegalArgumentException(); for (Iterator<? extends E> it = c.iterator(); it.hasNext();) add(it.next()); }
常用方法
boolean add(E e):将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
public boolean add(E e) { //调用的AbstractQueue.add方法,实际在父类调用了本类的offer方法 return super.add(e);}
boolean offer(E e): 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
public boolean offer(E e) { //判断元素是否为空 if (e == null) throw new NullPointerException(); //获取当前锁 final ReentrantLock lock = this.lock; //获取锁(若未获得,会一直去获取,直到取到锁为止) lock.lock(); try { //判断当前数组中元素数和数组长度是否相等 //相等,说明数组已满,无法添加新元素,直接返回false if (count == items.length) return false; else { //数组未满,添加新元素 insert(e); return true; } } finally { //释放锁 lock.unlock(); }}/** * 插入元素 */private void insert(E x) { items[putIndex] = x;//将元素放入到队尾 putIndex = inc(putIndex);//计算下一个待插入元素的index ++count;//更新当前元素数 notEmpty.signal();//唤醒take相关的等待线程。}/** * 计算下一个待插入元素的index */final int inc(int i) { return (++i == items.length)? 0 : i;}
boolean remove(Object o): 从此队列中移除指定元素的单个实例(如果存在)。
public boolean remove(Object o) { if (o == null) return false;//元素非空判断 final E[] items = this.items;//获取当前元素组 final ReentrantLock lock = this.lock; lock.lock();//获取锁 try { int i = takeIndex;//获取下一个被take的索引 int k = 0; for (;;) { if (k++ >= count)//循环次数大于实际元素数,退出循环 return false; if (o.equals(items[i])) {//判断是否是被移除的元素 removeAt(i);//移除元素 return true; } i = inc(i);//计算下一个被放入的元素 } } finally { lock.unlock();//释放锁 }}/** * 移除指定index的元素 * 若移除最后一个元素,tabkeIndex重置为0 * 若移除0-[count-1]的任意元素,索引i后面的元素依次前移 */ void removeAt(int i) { final E[] items = this.items;//获取当前元素组 // if removing front item, just advance if (i == takeIndex) {//刚刚移除队尾元素 items[takeIndex] = null;//元素置空 takeIndex = inc(takeIndex);//元素全部移除,takeIndex为0 } else { // slide over all others up through putIndex. for (;;) { int nexti = inc(i);//被移除元素的下一个元素的index if (nexti != putIndex) {//下一个元素待插入的index items[i] = items[nexti];//所以i后面的元素前移 i = nexti; } else { items[i] = null;//队尾元素置空 putIndex = i;//更新实际下一个add的index为先前的队尾 break; } } } --count;//更新实际元素数 notFull.signal();//唤醒add相关的等待线程。 }
E poll():获取并移除此队列的头,如果此队列为空,则返回 null。
public E poll() { final ReentrantLock lock = this.lock; lock.lock();//阻塞,获取锁 try { if (count == 0)//队列为空,不需要移除 return null; E x = extract();//移除元素 return x;//返回被移除的元素 } finally { lock.unlock();//释放锁 }} /** * 移除并返回被移除的元素,获取到锁的前提下,调用此方法 */ private E extract() { final E[] items = this.items; E x = items[takeIndex];//获取下一个takeIndex的元素 items[takeIndex] = null;//元素置空 takeIndex = inc(takeIndex);//计算下一个takeIndex --count;//更新元素数 notFull.signal();//唤醒add相关线程 return x;//返回被移除的元素 }
E peek():获取但不移除此队列的头;如果此队列为空,则返回 null。
public E peek() { final ReentrantLock lock = this.lock; lock.lock();//阻塞,获取锁 try { //无元素,返回空,否则返回下一个待获取的元素 return (count == 0) ? null : items[takeIndex]; } finally { lock.unlock();//释放锁 } }
void put(E e):将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException();//非空校验 final E[] items = this.items;//获取当前元素组 final ReentrantLock lock = this.lock; lock.lockInterruptibly();//阻塞获取锁,但响应中断 try { try { while (count == items.length)//队列已满 notFull.await();//是否已满进行等待 } catch (InterruptedException ie) { notFull.signal(); // 唤醒没有中断的线程 throw ie;//抛出线程中断异常 } insert(e);//队列未满,插入元素 } finally { lock.unlock();//释放锁 } }
E take():获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly();//阻塞时获取锁,但响应中断 try { try { while (count == 0) notEmpty.await();//队列为空,进行等待 } catch (InterruptedException ie) { notEmpty.signal(); // 唤醒非中断线程 throw ie; } E x = extract();//移除元素 return x;//并返回被移除的元素 } finally { lock.unlock();//释放锁 } }
由源码可知,ArrayBlockingQueue利用可重入的互斥锁ReentrantLock保证线程安全,并且利用Condition进行阻塞式take或者put。poll或offer支持两种操作限制:(Condition.await() 一直阻塞等待, awaitNanos(long nanosTimeout) 有时间限制的阻塞等待)
LinkedBlockingQueue
public class LinkedBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable
- 一个基于已链接节点的、范围任意的 blocking queue。
- 队列按 FIFO(先进先出)排序元素。
- 链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
- 可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。
成员变量
static class Node<E> {//静态内部类 /** * 当前元素 */ E item; /** * 当前元素的下一个元素 */ Node<E> next; Node(E x) { item = x; } } /** 链表队列最大容量 */ private final int capacity; /** 当前元素数 */ private final AtomicInteger count = new AtomicInteger(0); /** 链表头节点 */ private transient Node<E> head; /** 链表尾节点 */ private transient Node<E> last; /** 针对take相关的可重复入的互斥锁 */ private final ReentrantLock takeLock = new ReentrantLock(); /** take等待条件 */ private final Condition notEmpty = takeLock.newCondition(); /** 针对put, offer相关的可重复入的互斥锁 */ private final ReentrantLock putLock = new ReentrantLock(); /** put等待条件 */ private final Condition notFull = putLock.newCondition();
构造方法
/** * 创建一个容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue。 */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } /** * 创建一个具有给定(固定)容量的 LinkedBlockingQueue。 */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException();//判断容量值是否合法 this.capacity = capacity;//更新容量最大值 last = head = new Node<E>(null);设置头尾节点为空元素 } /** * 创建一个容量是 Integer.MAX_VALUE 的 LinkedBlockingQueue,最初包含给定 collection 的元素,元素按该 collection 迭代器的遍历顺序添加。 */ public LinkedBlockingQueue(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // 一直阻塞,直到获取锁 try { int n = 0; for (E e : c) {//加强循环c if (e == null)//元素不能为null throw new NullPointerException(); if (n == capacity)//队列已满 throw new IllegalStateException("Queue full"); enqueue(e);//将元素放入队列 ++n;//元素数+1 } count.set(n);//设置当前元素数 } finally { putLock.unlock();//释放锁 } } /** * 将元素插入到链表尾部 */ private void enqueue(E x) { last = last.next = new Node<E>(x); }
常用方法
boolean offer(E e):将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
public boolean offer(E e) { if (e == null) throw new NullPointerException();//非空检查 final AtomicInteger count = this.count;//获取当前元素数 if (count.get() == capacity)//判断元素数是否已到阀值 return false; int c = -1; final ReentrantLock putLock = this.putLock; putLock.lock();//获取add锁 try { if (count.get() < capacity) {//判断元素数是否已到阀值 enqueue(e);//插入元素到链表尾部 c = count.getAndIncrement();//获取add前元素数,并且更新元素数 if (c + 1 < capacity)//元素数没到阀值,唤醒add相关线程 notFull.signal(); } } finally { putLock.unlock();//释放锁 } if (c == 0)//说明元素已插入,唤醒take相关线程 signalNotEmpty(); return c >= 0;//返回是否已插入成功 } /** * 唤醒非空的线程 */ private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock();//阻塞式获取take锁 try { notEmpty.signal();//唤醒take相关线程 } finally { takeLock.unlock();//释放锁 } }
E peek():获取但不移除此队列的头;如果此队列为空,则返回 null。
public E peek() { if (count.get() == 0)//判断队列中是否已无元素 return null; final ReentrantLock takeLock = this.takeLock; takeLock.lock();//阻塞式获取take锁 try { Node<E> first = head.next;//获取头节点的下一个元素 if (first == null)// return null; else return first.item;//返回 } finally { takeLock.unlock(); } }
void put(E e):将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException();//非空判断 int c = -1;//判断是否添加成功 final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count;//获取当前元素数 putLock.lockInterruptibly();//获取add锁,可响应线程中断 try { while (count.get() == capacity) { //链表队列已满,线程等待 notFull.await(); } enqueue(e);//插入元素到链表尾部 c = count.getAndIncrement();//获取插入前的元素数,并+1 if (c + 1 < capacity)//插入后未超阀值,唤醒add相关线程 notFull.signal(); } finally { putLock.unlock();//释放锁 } if (c == 0) signalNotEmpty();//说明已插入元素,唤醒take相关线程 }
E poll():获取并移除此队列的头,如果此队列为空,则返回 null。
public E poll() { final AtomicInteger count = this.count; if (count.get() == 0)//判断队列中是否还有元素 return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock();//阻塞式获取take锁 try { if (count.get() > 0) { x = dequeue();//获取并移除元素 c = count.getAndDecrement();//更新元素数 if (c > 1) notEmpty.signal();//说明队列中还有元素,唤醒take相关线程 } } finally { takeLock.unlock();//释放锁 } if (c == capacity) signalNotFull();//移除了一个元素,说明队列未满,唤醒add线程 return x;//返回被移除的元素 } /** * 从队列头部移除元素(实际头部元素为:head.next) */ private E dequeue() { Node<E> h = head;//获取头节点 //获取头节点的下一个元素(实际是头节点,因为head.item始终为null) Node<E> first = h.next; h.next = h; // help GC(此时head.next指向自身,相当于head.next元素未被引用,便于垃圾回收) head = first;// 将下一个元素前移 E x = first.item;//获取实际头节点元素 first.item = null;//头节点元素置空(即head.item=null) return x;//返回头节点元素 }
E take():取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly();//阻塞式获取take锁,可响应线程中断 try { while (count.get() == 0) {//队列中无元素,线程等待 notEmpty.await(); } x = dequeue();//获取并移除元素 c = count.getAndDecrement();//更新元素数,返回移除前元素数 if (c > 1) notEmpty.signal();//队列中还有元素,唤醒take相关线程 } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
在看源码时,出现过疑惑,即head和last是怎么以链表形式连接起来的。此处要求大家需要区分基本数据类型和引用类型的区别。
具体截取的部分核心源码
//内部类 元素以nodex形式在链表中 static class Node<E> { E item; Node<E> next; Node(E x) { item = x; } } //构造LinkedBlockingQueue实例时,都会执行下面这行代码 last = head = new Node<E>(null); //ps:此时last和head的引用地址是相同的。 //上面代码可分解非下面三行: Node<E> node0 = new Node<E>(null); head = node0; last = head; //插入类方法最终会调用下面这 代码: last = last.next = new Node<E>(x); //可拆分为下面三行: Node<E> node1 = new Node<E>(x); last.next = node1;//该行执行完毕时,last.next为node1,同时 head.next也为node1;引用地址的特性, last = last.next;//此时改变last引用地址,head与last引用地址不相同,且last.next为null; // head=node0 但值有变化 // add前:head.item=null,head.next=node0; // add后:head.item=null,head.next=node1; // last= node0变为:last= node1 // add前:last.item=null,last.next=node0; // add后:last.item=x,last.next=null; //由此每次add,通过node.next构成链表式队列。head.item始终为null//接下来再看看出队(FIFO),从队头开始 public E dequeue() { Node<E> h = head;//获取头节点 Node<E> first = h.next; h.next = h; // 此时相当于h.next指向自身,帮助GC回收node0即头节点 head = first;// 将下一个元素前移 E x = first.item;//获取实际头节点元素 first.item = null;//头节点元素置空(即head.item=null) return x;//返回头节点元素 }
由此看出,LinkedBlockingQueue利用两个锁ReentrantLock(takeLock、putLock)、两个Condition(notEmpty、notFull)进行读写分离,提高效率,利用Node构成链表式队列。
- Java常见集合框架(十五):Queue之ArrayBlockingQueue、LinkedBlockingQueue
- Java常见集合框架(十四):Queue之Queue、BlockingQueue、AbstractQueue
- Java常见集合框架(十七):Queue总结
- Java常见集合框架(十六):Queue之DelayQueue、PriorityQueue、PriorityBlockingQueue
- 集合框架 Queue---ArrayBlockingQueue
- 集合框架 Queue---LinkedBlockingQueue
- Java常见集合框架(十):Set之TreeSet、HashSet
- Java常见集合框架(十九): Map之Map、AbstractMap、HashMap
- Java常见集合框架(十八): Deque之Deque、BlockingDeque、LinkedBlockingDeque、ArrayDeque
- Java常见集合框架(二十二): Map之Dictionary、Hashtable
- Java常见集合框架(十二):Set之ConcurrentSkipListSet、EnumSet
- Java常见集合框架(二十): Map之LinkedHashMap、SortedMap、NavigableMap、TreeMap
- 黑马程序员——Java集合框架—Queue—BlockingQueue—ArrayBlockingQueue
- 深入剖析java并发之阻塞队列LinkedBlockingQueue与ArrayBlockingQueue
- 黑马程序员——Java集合框架—Queue—BlockingQueue—LinkedBlockingQueue
- Java常见集合框架(五):List之CopyOnWriteArrayList
- Java LinkedBlockingQueue和ArrayBlockingQueue分析
- Java LinkedBlockingQueue和ArrayBlockingQueue分析 .
- R语言实践之简单范例
- 线上故障之load Average和cpu使用率的分析
- java
- MySQL/Mariadb基准测试工具-tpcc-mysql使用3
- eclipse git 拉取远程项目到本地
- Java常见集合框架(十五):Queue之ArrayBlockingQueue、LinkedBlockingQueue
- ServletAndJsp学习笔记--2
- ThinkPHP5验证码图片生成
- mysql创建新的用户及flush privileges解析
- Kafka 0.9 新版本consumer客户端使用介绍
- 使用函数实现两个数的交换
- 生产环境tomcat 在liunx系统中shutdown后进程仍然存在解决办法
- mapreduce 详解
- 成绩表成绩排序