Java多線程之等待隊(duì)列DelayQueue詳解
一. 概念
DelayQueue被稱作"等待隊(duì)列"或"JDK延遲隊(duì)列",存放著實(shí)現(xiàn)了Delayed接口的對象。對象需要設(shè)置到期時(shí)間,當(dāng)且僅當(dāng)對象到期,才能夠從隊(duì)列中被取走(并非一定被取走)。DelayQueue的內(nèi)部使用了PriorityQueue來存放元素,需要元素實(shí)現(xiàn)Comparable接口,優(yōu)先級隊(duì)列會根據(jù)對象的到期時(shí)間實(shí)現(xiàn)有序排序。

二. 案例
本案例參考了《Java編程思想》第21章P726頁的例子。
1. 定義延遲任務(wù)對象
class DelayedTask implements Runnable, Delayed { // Delayed接口必須實(shí)現(xiàn),Runnable接口可以不實(shí)現(xiàn)
private static int counter = 0;
private final int id = counter++;
/**
* 延遲的時(shí)間(單位: 毫秒)
*/
private final int delta;
/**
* 任務(wù)準(zhǔn)備執(zhí)行的時(shí)間點(diǎn)(單位: 納秒)
*/
private final long trigger;
protected static List<DelayedTask> sequence = new ArrayList<>();
public DelayedTask(int delayInMilliseconds) {
delta = delayInMilliseconds;
trigger = System.nanoTime() + NANOSECONDS.convert(delta, MILLISECONDS);
sequence.add(this);
}
@Override
public long getDelay(@NotNull TimeUnit unit) {
// 過期時(shí)間
return unit.convert(trigger - System.nanoTime(), NANOSECONDS);
}
/**
* 用于在過期任務(wù)隊(duì)列中,任務(wù)之間執(zhí)行順序的排序
*/
@Override
public int compareTo(@NotNull Delayed arg) {
DelayedTask that = (DelayedTask)arg;
if(trigger < that.trigger) return -1;
if(trigger > that.trigger) return 1;
return 0;
}
@Override
public void run() {
System.out.println(this + " ");
}
@Override
public String toString() {
// 語法規(guī)則: %[argument_index$][flags][width][.precision]conversion
// %后面的1$指的是第一個(gè)參數(shù),也就是delta
// $后面的-4d指的是如果數(shù)據(jù)總長度不夠4位,則由右向左補(bǔ)足空格
return String.format("[%1$-4d]", delta) + " Task " + id;
}
public String summary() {
return "(" + id + ":" + delta + ")";
}
public static class EndSentinel extends DelayedTask {
private ExecutorService exec;
public EndSentinel(int delay, ExecutorService e) {
super(delay);
exec = e;
}
@Override
public void run() {
for(DelayedTask pt : sequence) {
System.out.println(pt.summary() + " ");
}
System.out.println();
System.out.println(this + " Calling shutdownNow()");
exec.shutdownNow();
}
}
}2. 定義隊(duì)列的消費(fèi)者
class DelayedTaskConsumer implements Runnable {
private DelayQueue<DelayedTask> q;
public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
this.q = q;
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
// DelayQueue take()
// 取出隊(duì)列中的head元素,若隊(duì)列中沒有任何延遲到期的元素存在,則該方法將會被阻塞
// 此時(shí)有兩種可能: 1. 隊(duì)列中沒有元素 2. 隊(duì)列中有元素,但都尚未過期
// 直到隊(duì)列中有延遲到期的元素為止
// 即便是不讓DelayedTask實(shí)現(xiàn)Runnable接口,本例的執(zhí)行結(jié)果也不會發(fā)生改變。
// 因?yàn)榇颂幹苯诱{(diào)用了run()方法,并沒有分配其他線程去驅(qū)動DelayedTask
q.take().run();
}
} catch (InterruptedException e) {
// Acceptable way to exit
System.out.println("Acceptable way to exit");
}
System.out.println("Finished DelayedTaskConsumer");
}
}3. main方法
public class DelayQueueDemo {
public static void main(String[] args) {
test2();
}
public static void test1() {
Random random = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue = new DelayQueue<>();
// Fill with tasks that have random delays:
for(int i = 0; i < 5; i++) {
queue.put(new DelayedTask(random.nextInt(5000)));
}
//使用DelayQueue take()的方式獲取任務(wù)
queue.add(new DelayedTask.EndSentinel(5000, exec));
exec.execute(new DelayedTaskConsumer(queue));
}
public static void test2() {
Random random = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue = new DelayQueue<>();
// Fill with tasks that have random delays:
for(int i = 0; i < 5; i++) {
queue.put(new DelayedTask(random.nextInt(5000)));
}
//使用DelayQueue poll()方式獲取任務(wù)
while(queue.size() != 0) {
// 每執(zhí)行一次DelayQueue poll()且返回的元素不是null,則DelayQueue等待隊(duì)列的元素個(gè)數(shù)會減一
DelayedTask task = queue.poll();
if(task != null) {
System.out.println(LocalDateTime.now(ZoneId.of("+08:00")));
}
}
}
}由于案例中給Random設(shè)置了種子,因此過期時(shí)間的值是可以預(yù)測的(每次執(zhí)行都保持一致)。test1()的執(zhí)行結(jié)果如下:
* [555 ] Task 1
* [961 ] Task 4
* [1693] Task 2
* [1861] Task 3
* [4258] Task 0
* (0:4258)
* (1:555)
* (2:1693)
* (3:1861)
* (4:961)
* (5:5000)
其中,[]方括號代表任務(wù)的執(zhí)行順序,()代表任務(wù)的創(chuàng)建順序。顯而易見,任務(wù)的創(chuàng)建順序與執(zhí)行順序沒有任何關(guān)系,任務(wù)嚴(yán)格按照延遲時(shí)間的長短運(yùn)行。
三. 總結(jié)
1. 延遲隊(duì)列中的對象只有到期后才能夠從隊(duì)列中被取走。(若沒有到期或隊(duì)頭元素為null,則DelayQueue會陷入阻塞,說白了就是循環(huán)等待,可以參考DelayQueue的take()方法,寫了一個(gè)for(;;) )
2. 對象并非到期后就會被立刻取走,每次取出(poll())的僅僅是到期元素中隊(duì)頭元素。
3. 任務(wù)的創(chuàng)建順序與任務(wù)的執(zhí)行順序沒有任何關(guān)系,延遲隊(duì)列中任務(wù)的排序順序與過期任務(wù)對Comparable接口compareTo()方法的具體實(shí)現(xiàn)有關(guān)。
四. 疑問
還是以下圖為例

若在插入c之前,時(shí)間過去了100納秒,c仍然應(yīng)該排列在b之前嗎?照此推論,只要把過期時(shí)長控制在500納秒以內(nèi),所有插入的任務(wù)都應(yīng)該在b之前執(zhí)行(因?yàn)檫^期時(shí)長比b短),這是不是非常不合理?既然時(shí)間過去了100納秒,為什么不將a的過期時(shí)長改變成900納秒,b的過期時(shí)長改變成400納秒,最后讓b在c之前執(zhí)行呢?
遺憾的是,在DelayQueue的源碼中,并沒有看到對容器PriorityQueue內(nèi)部元素有做任何定時(shí)器,試圖改變?nèi)蝿?wù)過期時(shí)長的代碼。
到此這篇關(guān)于Java多線程之等待隊(duì)列DelayQueue詳解的文章就介紹到這了,更多相關(guān)Java的等待隊(duì)列DelayQueue內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)Redisson看門狗機(jī)制
redission看門狗機(jī)制是解決分布式鎖的續(xù)約問題,本文就來詳細(xì)的介紹一下java實(shí)現(xiàn)Redisson看門狗機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下2024-09-09
Spring?Security實(shí)現(xiàn)添加圖片驗(yàn)證功能
這篇文章主要為大家介紹了Spring?Security實(shí)現(xiàn)添加圖片驗(yàn)證功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Java實(shí)現(xiàn)讀取鍵盤輸入保存到txt文件,再統(tǒng)計(jì)并輸出每個(gè)單詞出現(xiàn)次數(shù)的方法
這篇文章主要介紹了Java實(shí)現(xiàn)讀取鍵盤輸入保存到txt文件,再統(tǒng)計(jì)并輸出每個(gè)單詞出現(xiàn)次數(shù)的方法,涉及java文件I/O操作及字符串遍歷、運(yùn)算實(shí)現(xiàn)統(tǒng)計(jì)功能相關(guān)技巧,需要的朋友可以參考下2017-07-07
SpringBoot項(xiàng)目接入Nacos的實(shí)現(xiàn)步驟
SpringBoot項(xiàng)目使用nacos作為配置中心和服務(wù)注冊中心,同時(shí)兼容dubbo的注冊中心。 本Demo項(xiàng)目使用的SpringBoot版本是2.3.9.RELEASE2021-05-05
SpringMVC獲取請求參數(shù)實(shí)現(xiàn)方法介紹
Spring MVC 是 Spring 提供的一個(gè)基于 MVC 設(shè)計(jì)模式的輕量級 Web 開發(fā)框架,本質(zhì)上相當(dāng)于 Servlet,Spring MVC 角色劃分清晰,分工明細(xì),這篇文章主要介紹了SpringMVC實(shí)現(xiàn)獲取請求參數(shù)方法2022-11-11
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(31)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07

