Java的延遲隊列(DelayQueue)是一種帶有延遲時間的阻塞隊列,最初在JDK1.5中引入。它允許我們向隊列中添加具有延遲時間的元素,并在元素到期後從隊列中擷取這些元素。
一、實作原理
Java 延遲隊列的實作基于 priority queue (優先級隊列),隊列中的元素根據到期時間排序。隊列頭部是最先到期的元素,每個元素都可以有不同的到期時間。DelayQueue 内部使用了堆排序算法,是以可以快速高效地查找、插入和删除元素。即使隊列中的元素數量非常龐大,它的性能也不會受到影響。
當添加一個元素到隊列中時,該元素會按照其到期時間插入到适當的位置,排成有序序列。當隊列中的元素到達到期時間時,該元素會從隊列頭部被移除。
每個元素都是一個實作了 java.util.concurrent.Delayed 接口的對象,該接口定義了兩個方法:
long getDelay(TimeUnit unit); // 傳回該元素還需等待的時間。
int compareTo(Delayed o); // 對元素進行比較,以便于維護過期元素的順序。
Java 延遲隊列 DelayQueue 實作了 BlockingQueue 接口,是以它具備了阻塞等待的能力,當嘗試取出一個元素時,如果隊列中沒有到期的元素,那麼目前線程會進入阻塞狀态,直到有一個元素過期或者插入一個過期的元素,喚醒目前線程進行取出操作。
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>();
......
}
二、代碼示例
定義了一個名為 DelayedElement 的類,它包含一個延遲時間和一個字元串 data。在構造函數中,我們計算出需要等待的時間,并将延遲時間設定為目前時間加上該時間。
實作了 Delayed 接口之後,我們需要實作 getDelay 和 compareTo 方法。 getDelay 方法傳回元素還需要等待的時間, compareTo 方法用于比較兩個元素的優先級,其中優先級由剩餘的延遲時間決定。
public class DelayedElement implements Delayed {
// 元素的到期時間
private long delayTime;
// 元素包含的資料
private String data;
/**
* 建立一個帶有延遲時間的元素對象
* @param delayTime 延遲時間,機關為毫秒
* @param data 包含的資料
*/
public DelayedElement(long delayTime, String data) {
// 計算元素的到期時間
this.delayTime = System.currentTimeMillis() + delayTime;
this.data = data;
}
/**
* 擷取元素的剩餘延遲時間
* @param unit 時間機關
* @return 元素的剩餘延遲時間
*/
@Override
public long getDelay(TimeUnit unit) {
// 計算元素到期時間與目前時間的時間差
long diff = delayTime - System.currentTimeMillis();
// 将時間差轉換為指定的時間機關
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
/**
* 比較兩個元素的到期時間,用于優先級隊列的排序
* @param o 要比較的另一個元素
* @return -1、0 或 1,分别代表該元素到期時間早于、等于或晚于另一個元素
*/
@Override
public int compareTo(Delayed o) {
if (this.delayTime < ((DelayedElement) o).delayTime) {
return -1;
}
if (this.delayTime > ((DelayedElement) o).delayTime) {
return 1;
}
return 0;
}
/**
* 傳回元素的字元串表示
* @return 元素的字元串表示
*/
@Override
public String toString() {
return "DelayedElement{" +
"delayTime=" + delayTime +
", data='" + data + '\'' +
'}';
}
}
在主函數中,首先建立一個 DelayQueue。然後,我們使用 put 方法将兩個 DelayedElement 對象放入隊列中。最後,我們一直從隊列中取元素并輸出它們,直到隊列為空。
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayedQueueExample {
public static void main(String[] args) throws InterruptedException {
// 建立 DelayQueue
DelayQueue<DelayedElement> queue = new DelayQueue<>();
// 将元素放入 DelayQueue,延遲1s
queue.put(new DelayedElement(1000, "Hello"));
// 将元素放入 DelayQueue,延遲5s
queue.put(new DelayedElement(5000, "World"));
// 擷取延遲元素并輸出
while (!queue.isEmpty()) {
DelayedElement element = queue.take();
System.out.println(element);
}
}
}
輸出結果:
DelayedElement{delayTime=1620478571034, data='Hello'}
DelayedElement{delayTime=1620478576031, data='World'}
第一個元素的延遲時間為1000毫秒,第二個元素的延遲時間為5000毫秒。
三、使用場景舉例
Java 的延遲隊列可以用在很多場景中,比如任務排程器。在某個時間點執行某個特定的任務,或者在某個時間段内以指定的頻率執行任務。Java 延遲隊列可以輕松地實作這樣的任務排程器。
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class TaskScheduler {
private DelayQueue<Task> queue = new DelayQueue<>();
public void schedule(Task task) {
queue.offer(task);
}
public void run() {
while (!queue.isEmpty()) {
try {
Task task = queue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Task implements Delayed {
private Runnable runnable;
private long executeTime;
public Task(Runnable runnable, long delay) {
this.runnable = runnable;
this.executeTime = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(executeTime, ((Task)o).executeTime);
}
}
}
我們可以在任務排程器中添加一個任務,該任務包含要執行的代碼塊和延遲時間。當調用 schedule 方法添加任務時,任務會按照其延遲時間插入到延遲隊列中。然後,我們可以調用 run 方法來運作任務排程器并等待任務執行。
四、優缺點
優點:
- 簡單易用:Java 的 DelayQueue 工具很容易使用,不需要過多的配置和大量的代碼。隻需簡單實作 Delayed 接口即可。
- 高效:基于優先級隊列的實作方式,DelayQueue 内部使用了堆排序算法,是以可以快速高效地查找、插入和删除元素。即使隊列中的元素數量非常龐大,它的性能也不會受到影響。
缺點:
- 不支援任務取消:一旦任務被添加到 DelayQueue 中,就無法取消或删除它,這可能會導緻不必要的資源占用。
- 對系統記憶體的消耗:DelayQueue 内部實作是一個優先級隊列,随着任務數量的增加,隊列大小會逐漸增大,這可能會占用大量的系統記憶體。
- 無法保證任務執行的精确時間:由于 DelayQueue 是基于時間的延遲機制,是以任務的執行時間不能保證精确,任務的實際執行時間可能比預期要早或者要晚。
五、總結
延遲隊列是一個非常有用的 Java 資料結構,它可以用于多種場景,包括緩存過期、任務逾時和心跳檢測等。在延遲隊列中,每個元素都有一個過期時間,當元素到期時,其相關操作被執行。Java的延遲隊列通過維護一個優先級隊列來實作,元素以一定的優先級順序存放在隊列中。從延遲隊列中擷取元素時,如果元素還沒有過期,将會被阻塞,直到元素到期或者被删除。在 Java 中,我們可以使用 java.util.concurrent.DelayQueue 來執行個體化延遲隊列。
對于本文的内容,不知道你有沒有什麼看法,歡迎在評論區裡留言。如果你對我的文章内容感興趣,請點選關注,謝謝支援![謝謝][謝謝][謝謝]