目錄
1.DelayQueue
1.1整體結構
- 延遲執行,並且可以設置延遲多久之後執行
- 隊列中元素將在過期時被執行,越靠近隊頭,越早過期
- 底層使用了優先級隊列來實現,複用組合了PriorityQueue(策略者模式),讓先過期的元素先執行
//隊列的元素必須爲Delayed本身或者子類
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
...
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 只有一把鎖,讀和寫同一時刻只能有一個在運行
private transient final ReentrantLock lock = new ReentrantLock();
...
}
//繼承了Comparable接口,用來比較大小
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
- DelayQueue 隊列中的元素必須是實現 Delayed 接口和 Comparable 接口的,並覆寫了 getDelay 方法和 compareTo 的方法纔行,不然在編譯時,編譯器就會提醒我們元素必須強制實現 Delayed 接口。
ps:只有一把鎖,讀和寫同一時刻只能有一個在運行
1.2放數據
public boolean offer(E e) {
final ReentrantLock lock = this.lock; //會鎖住當前整個實例
// 上鎖
lock.lock();
try {
// 使用 PriorityQueue 的擴容,排序
q.offer(e);
// 如果恰好剛放進去的元素正好在隊列頭,需要喚醒 take 的阻塞線程
// 空隊列一定也會進入判斷,因爲一定是隊列頭!!!
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
// 釋放鎖
lock.unlock();
}
}
DelayQueue的offer方法核心塊使用了PriorityQueue的offer方法
加鎖,使用PriorityQueue的offer方法判斷擴容並且按序插入,如果元素恰好放在隊列頭就喚醒一個take線程
// PriorityQueue的offer方法:新增元素
public boolean offer(E e) {
// 如果是空元素的話,拋異常
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
// 隊列實際大小大於容量時,進行擴容
// 擴容策略是:如果老容量小於 64,2 倍擴容,如果大於 64,50 % 擴容
if (i >= queue.length)
grow(i + 1);
size = i + 1;
// 如果隊列爲空,當前元素正好處於隊頭
if (i == 0)
queue[0] = e;
else
// 如果隊列不爲空,插入堆,從下向上冒泡
siftUp(i, e);
return true;
}
// 維護一個最小堆!!!
// 新增操作:從下向上冒泡的過程不需要左右節點在比較的!!!
// 刪除操作:從上向下冒泡的過程是需要左右節點比較然後在進行交換的!!!
private void siftUp(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
// k 是當前隊列實際大小的位置
while (k > 0) {
// 對 k 進行減倍
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 如果 x 比 e 大,退出,把 x 放在 k 位置上
if (key.compareTo((E) e) >= 0)
break;
// x 比 e 小,繼續循環,直到找到 x 比隊列中元素大的位置
queue[k] = e;
k = parent;
}
queue[k] = key;
}
PriorityQueue的offer方法:先判斷是否需要擴容(如果元素個數少於64每次擴容2倍,否則每次擴容1.5倍),如果隊列爲空那麼直接在頭節點新增即可,如果不爲空,就需要進行堆插入從下向上進行冒泡,不斷的跟父節點進行比較即可,最後返回true新增成功!
ps:因爲本身就是一個堆,所以不需要在判斷另一個節點的值,這一點跟從上向下冒泡不同,後者需要比較出左右節點的最值!
ps:堆排序首先需要維護一個堆(從下向上對每個非葉子節點自上向下冒泡維護一個最大堆),然後進行實際的排序(每次與堆尾部交換頭節點,然後從堆頭自上向下維護堆結構)
1.3拿數據
//take方法:無限阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock; //獲取鎖,會鎖住整個實例
lock.lockInterruptibly();// 可中斷鎖
for (;;) {
// 從隊頭中拿數據出來
E first = q.peek();
// 如果爲空,說明隊列中,沒有數據,阻塞住
if (first == null)
available.await();
else {
// 獲取隊頭數據的過期時間
long delay = first.getDelay(NANOSECONDS);
// 如果過期了,直接返回隊頭數據
if (delay <= 0)
return q.poll();
// 引用置爲 null ,便於 gc,這樣可以讓線程等待時,回收 first 變量
first = null;
// leader 不爲空的話,表示當前隊列元素之前已經被設置過阻塞時間了
// 已經被設置阻塞時間不會進入這裏吧!!!!finally不是保證重置了嗎???
if (leader != null)
available.await();
else {
// 之前沒有設置過阻塞時間,按照一定的時間進行阻塞
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 進行阻塞,等待delay延遲時間
available.awaitNanos(delay);
} finally {//一定會重置
if (leader == thisThread)
leader = null;
}
}
}finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
加鎖並且設置爲可中斷,如果隊列爲空就會阻塞,如果不爲空會先獲取延遲時間,如果延時過期(可以執行了)就刪除頭節點並且執行,如果沒有設置阻塞時間會進行延時等待