并发包中的并发队列
一、ConcurrentLinkedQueue——无界非阻塞队列(链式存储)
ConcurrentLinkedQueue内部的队列是单链表实现的,
1.链表节点:静态内部类Node,没有其他设计,仅unsafe保证原子性
private static class Node<E> { volatile E item; volatile Node<E> next; Node(E item) { UNSAFE.putObject(this, itemOffset, item); } boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } boolean casNext(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } private static final sun.misc.Unsafe UNSAFE; private static final long itemOffset; private static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Node.class; itemOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("item")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }
2.变量与构造方法
private transient volatile Node<E> head;//单链表头节点 private transient volatile Node<E> tail;//单链表尾节点 public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); } public ConcurrentLinkedQueue(Collection<? extends E> c) { Node<E> h = null, t = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); t = newNode; } } if (h == null) h = t = new Node<E>(null); head = h; tail = t; }
3.方法
bealoon offer(E e):入队
public boolean offer(E e) { //非空检查 checkNotNull(e); //创建节点Node对象,构造方法中unsafe保证原子性 final Node<E> newNode = new Node<E>(e); //从尾节点入队 for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // q == null 说明p是尾节点 CAS入队 if (p.casNext(null, newNode)) { //与下面else,两次offer后tail指向尾节点 if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } else if (p == q) //poll引起的 p = (t != (t = tail)) ? t : head; else // Check for tail upda tes after two hops. p = (p != t && t != (t = tail)) ? t : q; } }
入队过程:offer两次后,tail才会指向尾节点
E poll():出队
public E poll() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; if (item != null && p.casItem(item, null)) { // Successful CAS is the linearization point // for item to be removed from this queue. if (p != h) // hop two nodes at a time updateHead(h, ((q = p.next) != null) ? q : p); return item; } else if ((q = p.next) == null) { updateHead(h, p); return null; } else if (p == q) continue restartFromHead; else p = q; } } }
出队,三种情景
1)空节点时,不变
2)一个节点时,头节点自循环,也就对应offer中p == q的情景
3)多个节点时,头节点自循环,head指针会一次跳过两个节点。
E peek():获取队首节点,不删除,但是第一次调用时会删除哨兵节点(初始化head、tail指向的空节点)
public E peek() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; if (item != null || (q = p.next) == null) { updateHead(h, p); return item; } else if (p == q) continue restartFromHead; else p = q; } } }
remove存在元素就删除第一个该元素
size和contains方法在并发环境下不常用,因为CAS未加锁,会导致结果不精确(保证最终一致性,不保证实时一致性)
二、LinkedBlockingQueue——有界阻塞队列(链式存储)
static class Node<E> { E item; Node<E> next; Node(E x) { item = x; } }
2.变量与构造方法
/** 链表容量 */ private final int capacity; /** 队列中元素数 */ private final AtomicInteger count = new AtomicInteger(); /** * 头节点(head.item == null) */ transient Node<E> head; /** * 尾节点(last.item == null) */ private transient Node<E> last; /** 出队独占锁 用于take、poll方法中 */ 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(); public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); } 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(); } }
3.方法
1)boolean offer(E e):非阻塞入队
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; Node<E> node = new Node<E>(e); //获取入队独占锁 final ReentrantLock putLock = this.putLock; putLock.lock(); try { if (count.get() < capacity) { //队列不满,元素入队 enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) //队列不满,唤醒notFull条件队列中的一个线程 notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) //队列为空时,唤醒notEmpty条件队列中的一个线程 signalNotEmpty(); return c >= 0; }
2)void put(E e):阻塞入队
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; //不忽略中断引起的返回 putLock.lockInterruptibly(); try { while (count.get() == capacity) { //队满时,当前线程入notFull条件队列 notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) //入队后,队列不满,唤醒因队满引起的阻塞线程(put方法引起) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) //队空时,唤醒因notEmpty条件队列中的一个阻塞线程 signalNotEmpty(); }
3)E poll():不阻塞出队
public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) //队列为空直接返回null,不入阻塞队列 return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) //队列不为空时,唤醒notEmpty条件队列中的一个线程 notEmpty.signal(); } } finally { takeLock.unlock(); } if (c == capacity) //出队成功后,队满,欢迎notFull条件队列中的一个线程 signalNotFull(); return x; }
4)E take():阻塞出队
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; //不忽略中断引起的返回,抛出异常 takeLock.lockInterruptibly(); try { while (count.get() == 0) { //队空时,入notEmpty条件阻塞队列,阻塞 notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) //出队后,队不为空,唤醒notEmpty条件阻塞队列中的一个线程 notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) //队满时,唤醒notFull条件阻塞队列中的一个线程 signalNotFull(); return x; }
5)E peek()非阻塞获取队首元素
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; else return first.item; } finally { takeLock.unlock(); } }
6)boolean remove( Object o):删除队列中指定元素
public boolean remove(Object o) { if (o == null) return false; //获取两个锁,takeLock及putLock,即会阻塞出入队操作 fullyLock(); try { for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) { if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { fullyUnlock(); } }
7).int size():队列元素个数
public int size() { return count.get();//takeLock和putLock已经保证了count的原子性 }
4.总结
1)单链表实现,
2)读写分离策略,引入两个可重入独占锁takeLock(出队锁)和putLock(入队锁)
3)提供入队的非阻塞方法offer和阻塞方法put,队满,调用offer入队时直接丢弃,调用put入队时,线程入putLock的条件变量notFull的阻塞队列,offer、put、poll、take都会唤醒notFull的 阻塞队列中的线程
另offer方法忽略中断引起的返回,put方法不忽略中断引起的返回,会抛出异常
4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入takeLock的条件变量notEmpty的阻塞队列,offer、put、poll、take都会唤醒 notEmpty的阻塞队列中的线程
另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常
5)remove操作会获取双重锁,即remove操作时,会阻塞其他出入队线程。
6)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列满空入条件队列(生产消费模型)
三、ArrayBlockingQueue——有界阻塞队列(顺序存储)
1.变量与构造方法
/** 存放队列元素的数组——顺序存储 */ final Object[] items; /** 队列头节点数组下标 items index for next take, poll, peek or remove */ int takeIndex; /** 入队时入队元素存入的数组下标(尾节点数组下标+1,注意循环队列顺序实现) items index for next put, offer, or add */ int putIndex; /** 队列中元素个数 Number of elements in the queue */ int count; /* * 并发控制采用单锁双条件控制 */ /** Main lock guarding all access */ final ReentrantLock lock; /** 出队时 队空,条件阻塞队列 Condition for waiting takes */ private final Condition notEmpty; /** 入队时 队满,条件阻塞队列 Condition for waiting puts */ private final Condition notFull; /** * 迭代器 */ transient Itrs itrs = null; public ArrayBlockingQueue(int capacity) { this(capacity, false); } /**构造方法初始化锁及条件变量*/ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion try { int i = 0; try { for (E e : c) { checkNotNull(e); items[i++] = e; } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i; } finally { lock.unlock(); } }
2.方法
1)boolean offer(E e):非阻塞入队
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) //队满直接返回,未入条件阻塞队列--非阻塞 return false; else { enqueue(e); return true; } } finally { lock.unlock(); } } private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; //数组实现的循环队列 if (++putIndex == items.length) putIndex = 0; count++; //唤醒notEmpty条件阻塞队列中的一个线程 notEmpty.signal(); }
2)void put(E e):阻塞入队
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; //不忽略中断引起的AQS阻塞队列的返回,抛出异常 lock.lockInterruptibly(); try { while (count == items.length) //队满入notFull条件阻塞队列--阻塞 notFull.await(); enqueue(e); } finally { lock.unlock(); } }
3)E poll():非阻塞出队
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; //数组实现的循环队列,头指针变化 if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) //迭代器中元素出队 itrs.elementDequeued(); //唤醒notFull条件阻塞队列中的一个线程 notFull.signal(); return x; }
4)E take():阻塞出队
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //不忽略中断引起的AQS阻塞队列中线程的返回 lock.lockInterruptibly(); try { while (count == 0) //队空时,入notEmpty条件阻塞队列--阻塞 notEmpty.await(); return dequeue(); } finally { lock.unlock(); } }
5)E peek():非阻塞获取队首元素
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } } final E itemAt(int i) { return (E) items[i]; }
6)int size():当前队列中元素个数
public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return count;//加锁原因:count内存可见性(未被volatile修饰) } finally { lock.unlock(); } }
三、总结
1.循环队列实现
2.单锁双条件变量的设计可能是由于读写操作的是同一个数组,与LinkedBlockingQueue双锁双条件变量区别,操作的是两个独立的节点
3)提供入队的非阻塞方法offer和阻塞方法put,队满,调用offer入队时直接丢弃,调用put入队时,线程入lock的条件变量notFull的阻塞队列,poll、take都会唤醒notFull的阻塞队列中的线程,另offer方法忽略中断引起的返回,put方法不忽略中断引起的返回,会抛出异常
4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入takeLock的条件变量notEmpty的阻塞队列,offer、put都会唤醒notEmpty的阻塞队列中的线程,另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常
5)size方法加锁,比LinkedBlockingQueue的size方法更加精确
6)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列满空入条件队列(生产消费模型),(循环队列,将数据画成圆形比较好,后补)
四、PriorityBlockingQueue——优先级无界阻塞队列(顺序存储、堆实现)
顺序存储实现的平衡二叉树堆结构,用堆来实现优先级,涉及比较所以元素需要实现Comparable接口
1、变量与构造方法
/** * 默认队列大小 */ private static final int DEFAULT_INITIAL_CAPACITY = 11; /** * 队列最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 实际存放队列元素的数组,大小可扩容 */ private transient Object[] queue; /** * 队列中元素的个数 */ private transient int size; /** * 比较器 */ private transient Comparator<? super E> comparator; /** * 独占锁 */ private final ReentrantLock lock; /** * 为空时条件阻塞队列 */ private final Condition notEmpty; /** * 自旋锁标识 扩容时使用 */ private transient volatile int allocationSpinLock; /** * 优先级队列、仅用于序列化 */ private PriorityQueue<E> q; /** * 默认构造*/ public PriorityBlockingQueue() { this(DEFAULT_INITIAL_CAPACITY, null); } public PriorityBlockingQueue(int initialCapacity) { this(initialCapacity, null); } public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.lock = new ReentrantLock();//初始化独占锁 this.notEmpty = lock.newCondition();//初始化为空时条件变量(无界的所以没有队满时条件变量) this.comparator = comparator;//初始化比较器 this.queue = new Object[initialCapacity];//初始化数组 } public PriorityBlockingQueue(Collection<? extends E> c) { this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); boolean heapify = true; // true if not known to be in heap order boolean screen = true; // true if must screen for nulls if (c instanceof SortedSet<?>) { SortedSet<? extends E> ss = (SortedSet<? extends E>) c; this.comparator = (Comparator<? super E>) ss.comparator(); heapify = false; } else if (c instanceof PriorityBlockingQueue<?>) { PriorityBlockingQueue<? extends E> pq = (PriorityBlockingQueue<? extends E>) c; this.comparator = (Comparator<? super E>) pq.comparator(); screen = false; if (pq.getClass() == PriorityBlockingQueue.class) // exact match heapify = false; } Object[] a = c.toArray(); int n = a.length; // If c.toArray incorrectly doesn't return Object[], copy it. if (a.getClass() != Object[].class) a = Arrays.copyOf(a, n, Object[].class); if (screen && (n == 1 || this.comparator != null)) { for (int i = 0; i < n; ++i) if (a[i] == null) throw new NullPointerException(); } this.queue = a; this.size = n; if (heapify) heapify(); }
2、常用方法
1)由于是无界队列所以元素入队不存在阻塞,offer(E e),put(E e)都是非阻塞的
public boolean offer(E e) { if (e == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); int n, cap; Object[] array; while ((n = size) >= (cap = (array = queue).length)) //队列元素大小超过队列大小时候数组扩容 tryGrow(array, cap); try { Comparator<? super E> cmp = comparator; if (cmp == null) //比较器为空默认元素e.compareTo(array[]),建堆与入堆调整 siftUpComparable(n, e, array); else //比较器compareTo(e,array[]),建堆与入堆调整 siftUpUsingComparator(n, e, array, cmp); size = n + 1; //入队成功,唤醒notEmpty条件阻塞队列中的一个线程 notEmpty.signal(); } finally { lock.unlock(); } return true; }
2)E poll():非阻塞出队
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return dequeue(); } finally { lock.unlock(); } } private E dequeue() { int n = size - 1; if (n < 0) return null; else { Object[] array = queue; E result = (E) array[0]; E x = (E) array[n]; array[n] = null; Comparator<? super E> cmp = comparator; if (cmp == null) siftDownComparable(0, x, array, n);//堆顶出队,然后调整堆 else siftDownUsingComparator(0, x, array, n, cmp); size = n; return result; } }
3)E take():阻塞出队
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //不忽略中断引起的AQS阻塞队列的返回,会抛出异常 lock.lockInterruptibly(); E result; try { while ( (result = dequeue()) == null) //队列为空,入notEmpty条件阻塞队列 notEmpty.await(); } finally { lock.unlock(); } return result; }
4)E peek():非阻塞获取队首元素
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return (size == 0) ? null : (E) queue[0]; } finally { lock.unlock(); } }
5)int size():获取队列中元素个数
public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return size; } finally { lock.unlock(); } }
3.两个重要的实现方法
1)数组扩容方法tryGrow
private void tryGrow(Object[] array, int oldCap) { //扩容时先释放锁,不释放会导致扩容线程扩容期间,其他线程操作会阻塞 lock.unlock(); // must release and then re-acquire main lock Object[] newArray = null; //allocationSpinLock标识实现自旋锁,1表示正在扩容 if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) { try { //扩容策略①当前数组大小小于64时 newCap = 2 * oldCap + 2 //扩容策略②当前数组大小不小于64时 newCap = 1.5 * oldCap int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) : // grow faster if small (oldCap >> 1)); if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow //先数组newCap大于扩容最大值,oldCap+1还是溢出的话抛出异常,否则newCap = 最大容量 int minCap = oldCap + 1; if (minCap < 0 || minCap > MAX_ARRAY_SIZE) throw new OutOfMemoryError(); newCap = MAX_ARRAY_SIZE; } //扩容期间 数组被其他线程修改,扩容失败,offer方法中while循环重试 if (newCap > oldCap && queue == array) newArray = new Object[newCap]; } finally { allocationSpinLock = 0; } } if (newArray == null) //正在扩容时,其他线程让出CPU back off if another thread is allocating Thread.yield(); //还原执行扩容的线程的锁状态 lock.lock(); if (newArray != null && queue == array) { queue = newArray; System.arraycopy(array, 0, newArray, 0, oldCap); } }
2)堆实现
建堆方法siftUpComparable
private static <T> void siftUpComparable(int k, T x, Object[] array) { Comparable<? super T> key = (Comparable<? super T>) x; while (k > 0) { int parent = (k - 1) >>> 1; Object e = array[parent]; if (key.compareTo((T) e) >= 0) break; array[k] = e; k = parent; } array[k] = key; }
例子:入堆序列15324,小顶堆
堆顶元素出堆siftDownComparable
private static <T> void siftDownComparable(int k, T x, Object[] array, int n) { if (n > 0) { Comparable<? super T> key = (Comparable<? super T>)x; int half = n >>> 1; // loop while a non-leaf while (k < half) { int child = (k << 1) + 1; // assume left child is least Object c = array[child]; int right = child + 1; if (right < n && ((Comparable<? super T>) c).compareTo((T) array[right]) > 0) c = array[child = right]; if (key.compareTo((T) c) <= 0) break; array[k] = c; k = child; } array[k] = key; } }
例子:入堆序列15324,小顶堆出堆
4、总结
1.顺序存储,二叉平衡树堆实现
2.由于是无界的所以仅有一个条件变量notEmpty,单锁单条件变量
3)入堆方法offer、put是非阻塞的(无notFull)
4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入lock的条件变量notEmpty的阻塞队列,offer、put都会唤醒notEmpty的阻塞队列中的线程,另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常
5)无界阻塞队列,所以存在扩容机制;用自旋锁保证扩容时的线程安全;扩容策略:
①oldCap<64时newCap = 2*oldCap +2;oldCap>=64时newCap = 1.5*oldCap;
②newCap>maxCap时oldCap+1>maxCap抛错;否则newCap=maxCap;
6)建堆与堆调整是《数据结构》中堆排序算法的简化。
7)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列空入条件队列(生产消费模型),数组中的元素不再是固定的,每次入队出队都会进行堆调整
五、DelayQueue——无界阻塞延时队列
采用PriorityQueue存储元素,延时队列故元素必须有个过期时间(必须实现Delayed接口)
public interface Delayed extends Comparable<Delayed> { long getDelay(TimeUnit unit); }
1、变量与构造方法
private final transient ReentrantLock lock = new ReentrantLock(); private final PriorityQueue<E> q = new PriorityQueue<E>(); /** * leader-follower线程模型的变体*/ private Thread leader = null; /** * 争取leader线程的阻塞队列 */ private final Condition available = lock.newCondition(); public DelayQueue() {} public DelayQueue(Collection<? extends E> c) { this.addAll(c); }
2、方法
1)无界队列,offer()、put()是非阻塞方法
public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { q.offer(e); if (q.peek() == e) { //元素入队且位于队首;由于leader线程与队首元素有关,队首元素改变了,需要重新设置 leader = null; available.signal(); } return true; } finally { lock.unlock(); } }
2)E poll():非阻塞出队;E peek():获取队首元素
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { E first = q.peek(); if (first == null || first.getDelay(NANOSECONDS) > 0) //没有过期元素时,直接返回——非阻塞 return null; else return q.poll(); } finally { lock.unlock(); } } public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return q.peek(); } finally { lock.unlock(); } }
3)E take():阻塞出队
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) //队列为空,入条件阻塞队列 和前面的notEmpty一样 available.await(); else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) //队首元素过期则出队 return q.poll(); first = null; // don't retain ref while waiting //队首元素未过期 if (leader != null) //leader线程不为空,当前线程为followed线程入条件阻塞队列 比notEmpty多行使的职责 available.await(); else { //leader线程为空,设置当前线程为leader线程 Thread thisThread = Thread.currentThread(); leader = thisThread; try { //leader线程入条件阻塞队列,有效期为过期时间, //此时入队线程会被lock阻塞入AQS阻塞队列,出队线程会被leader线程阻塞,进入available条件阻塞队列 available.awaitNanos(delay); } finally { //过期时间到,取消leader线程,线程出队 if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) //出队成功后 leader线程为空且队列不为空,唤醒其他出队线程(followed线程) available.signal(); lock.unlock(); } }
3.总结
1).由优先级队列PriorityQueue存储数据,PriorityQueue是java.util下的集合,不是线程安全的,用独占锁保证线程安全;入队元素需要实现Delayed接口
2).单锁单条件变量available;这里的条件变量available两种情景
①队列为空时,take出队线程入条件阻塞队列(与notEmpty场景一致)
②队列不为空时,take出队但没有过期元素,入条件阻塞队列
3).leader-followed线程模型的简易变体,队列不为空且多个take线程但没有过期元素,线程全部入条件阻塞队列,并会设置一个线程为leader线程,其他线程为followed线程;
leader线程由于awaitNanos(delay)出队成功后会唤醒其他followed线程;争取leader线程
4)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列空或没有过期元素入条件队列(生产消费模型)
4、实例
public class DelayQueueTest { public static void main(String[] args){ DelayQueue<DelayedEle> delayQueue = new DelayQueue<>(); Random random = new Random(); for (int i = 0; i < 10; i++) { DelayedEle ele = new DelayedEle(random.nextInt(500), "task" + i); delayQueue.offer(ele); } DelayedEle eleTake = null; try { for (;;){ while ((eleTake = delayQueue.take()) != null){ System.out.println(eleTake.toString()); } } }catch (InterruptedException e){ e.printStackTrace(); } } static class DelayedEle implements Delayed { private final long delayTime; private final long expire; private String taskName; public DelayedEle(long delayTime, String taskName){ this.delayTime = delayTime; this.taskName = taskName; this.expire = System.currentTimeMillis() + delayTime; } @Override public long getDelay(TimeUnit unit) { return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); } @Override public String toString() { final StringBuilder sb = new StringBuilder("DelayedEle{"); sb.append("delay=").append(delayTime); sb.append(", expire=").append(expire); sb.append(", taskName=").append(taskName); sb.append("}"); return sb.toString(); } } }
参考自《java并发编程之美》