【Java並發編程】19、DelayQueue源碼分析,delayqueue源碼
DelayQueue,帶有延遲元素的安全執行緒隊列,當非阻塞從隊列中擷取元素時,返回最早達到延遲時間的元素,或空(沒有元素達到延遲時間)。DelayQueue的泛型參數需要實現Delayed介面,Delayed介面繼承了Comparable介面,DelayQueue內部使用非安全執行緒的優先隊列(PriorityQueue),並使用Leader/Followers模式,最小化不必要的等待時間。DelayQueue不允許包含null元素。
領導者/追隨者模式是多個背景工作執行緒輪流獲得事件來源集合,輪流監聽、分發並處理事件的一種模式。在任意時間點,程式都僅有一個領導者線程,它負責監聽IO事件。而其他線程都是追隨者,它們休眠線上程池中等待成為新的領導者。當前的領導者如果檢測到IO事件,首先要從線程池中推選出新的領導者線程,然後處理IO事件。此時,新的領導者等待新的IO事件,而原來的領導者則處理IO事件,二者實現了並發。
簡單理解,就是最多隻有一個線程在處理,其他線程在睡眠。在DelayQueue的實現中,Leader/Followers模式用於等待隊首的第一個元素。
類定義及參數:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> { /** 重入鎖,實現安全執行緒 */ private final transient ReentrantLock lock = new ReentrantLock(); /** 使用優先隊列實現 */ private final PriorityQueue<E> q = new PriorityQueue<E>(); /** Leader/Followers模式 */ private Thread leader = null; /** 條件對象,當新元素到達,或新線程可能需要成為leader時被通知 */ private final Condition available = lock.newCondition();
建構函式:
/** * 預設構造,得到空的延遲隊列 */ public DelayQueue() {} /** * 構造延遲隊列,初始包含c中的元素 * * @param c 初始包含的元素集合 * @throws NullPointerException 當集合或集合任一元素為空白時拋出null 指標錯誤 */ public DelayQueue(Collection<? extends E> c) { this.addAll(c); }
add方法:
/** * 向延遲隊列插入元素 * * @param e 要插入的元素 * @return true * @throws NullPointerException 元素為空白,拋出null 指標錯誤 */ public boolean add(E e) { // 直接調用offer並返回 return offer(e); }
offer方法:
/** * 向延遲隊列插入元素 * * @param e 要插入的元素 * @return true * @throws NullPointerException 元素為空白,拋出null 指標錯誤 */ public boolean offer(E e) { final ReentrantLock lock = this.lock; // 獲得鎖 lock.lock(); try { // 向優先隊列插入元素 q.offer(e); // 若在此之前隊列為空白,則置空leader,並通知條件對象,需要結合take方法看 if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { // 釋放鎖 lock.unlock(); } }
put方法:
/** * 向延遲隊列插入元素. 因為隊列是無界的,所以不會阻塞。 * * @param e 要插入的元素 * @throws NullPointerException 元素為空白,拋出null 指標錯誤 */ public void put(E e) { offer(e); }
帶逾時的offer方法:
/** * 向延遲隊列插入元素. 因為隊列是無界的,所以不會阻塞,因此,直接調用offer方法並返回 * * @param e 要插入的元素 * @param timeout 不會阻塞,忽略 * @param unit 不會阻塞,忽略 * @return true * @throws NullPointerException 元素為空白,拋出null 指標錯誤 */ public boolean offer(E e, long timeout, TimeUnit unit) { // 直接調用offer方法並返回 return offer(e); }
poll方法:
/** * 擷取並移除隊首的元素, 或者返回null(如果隊列不包含到達延遲時間的元素) * * @return 隊首的元素, 或者null(如果隊列不包含到達延遲時間的元素) */ public E poll() { final ReentrantLock lock = this.lock; // 獲得鎖 lock.lock(); try { // 擷取優先隊列隊首元素 E first = q.peek(); // 若優先隊列隊首元素為空白,或者還沒達到延遲時間,返回null if (first == null || first.getDelay(NANOSECONDS) > 0) return null; // 否則,返回並移除隊首元素 else return q.poll(); } finally { // 釋放鎖 lock.unlock(); } }
take方法(重要):
/** * 擷取並移除隊首元素,該方法將阻塞,直到隊列中包含達到延遲時間的元素 * * @return 隊首元素 * @throws InterruptedException 阻塞時被打斷,拋出打斷異常 */ public E take() throws InterruptedException { final ReentrantLock lock = this.lock; // 獲得鎖,該鎖可被打斷 lock.lockInterruptibly(); try { // 迴圈處理 for (;;) { // 擷取隊首元素 E first = q.peek(); // 若元素為空白,等待條件,在offer方法中會調用條件對象的通知方法 // 並重新進入迴圈 if (first == null) available.await(); // 若元素不為空白 else { // 擷取延遲時間 long delay = first.getDelay(NANOSECONDS); // 若達到延遲時間,返回並移除隊首元素 if (delay <= 0) return q.poll(); // 否則,需要進入等待 first = null; // 在等待時,不持有引用 // 若leader不為空白,等待條件 if (leader != null) available.await(); // 否則,設定leader為當前線程,並逾時等待延遲時間 else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { // 通知其他線程條件得到滿足 if (leader == null && q.peek() != null) available.signal(); // 釋放鎖 lock.unlock(); } }
帶逾時的poll方法(重要):
/** * 擷取並移除隊首元素,該方法將阻塞,直到隊列中包含達到延遲時間的元素或逾時 * * @return 隊首元素,或者null * @throws InterruptedException 阻塞等待時被打斷,拋出打斷異常*/ public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) { if (nanos <= 0) return null; else nanos = available.awaitNanos(nanos); } else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); if (nanos <= 0) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) nanos = available.awaitNanos(nanos); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { long timeLeft = available.awaitNanos(delay); nanos -= delay - timeLeft; } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
peek方法:
/** * 擷取但不移除隊首元素,或返回null(如果隊列為空白)。和poll方法不同, * 若隊列不為空白,該方法換回隊首元素,不論是否達到延遲時間 * * @return 隊首元素,或null(如果隊列為空白) */ public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return q.peek(); } finally { lock.unlock(); } }
出處:
https://www.cnblogs.com/enumhack/p/7472873.html
https://www.cnblogs.com/wanly3643/p/3944661.html
jdk源碼