偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

面試侃集合 | DelayQueue篇

開發(fā) 前端
今天我們?cè)賮砹牧暮退嚓P(guān)的DelayQueue吧?DelayQueue也是一個(gè)無界阻塞隊(duì)列,但是和之前我們聊的其他隊(duì)列不同,不是所有類型的元素都能夠放進(jìn)去,只有實(shí)現(xiàn)了Delayed接口的對(duì)象才能放進(jìn)隊(duì)列。

[[407606]]

面試官:好久不見啊,上次我們聊完了PriorityBlockingQueue,今天我們?cè)賮砹牧暮退嚓P(guān)的DelayQueue吧?

Hydra:就知道你前面肯定給我挖了坑,DelayQueue也是一個(gè)無界阻塞隊(duì)列,但是和之前我們聊的其他隊(duì)列不同,不是所有類型的元素都能夠放進(jìn)去,只有實(shí)現(xiàn)了Delayed接口的對(duì)象才能放進(jìn)隊(duì)列。Delayed對(duì)象具有一個(gè)過期時(shí)間,只有在到達(dá)這個(gè)到期時(shí)間后才能從隊(duì)列中取出。

面試官:有點(diǎn)意思,那么它有什么使用場(chǎng)景呢?

Hydra:不得不說,由于DelayQueue的精妙設(shè)計(jì),使用場(chǎng)景還是蠻多的。例如在電商系統(tǒng)中,如果有一筆訂單在下單30分鐘內(nèi)沒有完成支付,那么就需要自動(dòng)取消這筆訂單。還有,如果我們緩存了一些數(shù)據(jù),并希望這些緩存在一定時(shí)間后失效的話,也可以使用延遲隊(duì)列將它從緩存中刪除。

以電商系統(tǒng)為例,可以簡(jiǎn)單看一下這個(gè)流程:

 

面試官:看起來和任務(wù)調(diào)度有點(diǎn)類似啊,它們之間有什么區(qū)別嗎?

Hydra:任務(wù)調(diào)度更多的偏向于定時(shí)的特性,是在指定的時(shí)間點(diǎn)或時(shí)間間隔執(zhí)行特定的任務(wù),而延遲隊(duì)列更多偏向于在指定的延遲時(shí)間后執(zhí)行任務(wù)。相對(duì)任務(wù)調(diào)度來說,上面舉的例子中的延遲隊(duì)列場(chǎng)景都具有高頻率的特性,使用定時(shí)任務(wù)來實(shí)現(xiàn)它們的話會(huì)顯得有些過于笨重了。

面試官:好了,你也白話了半天了,能動(dòng)手就別吵吵,還是先給我寫個(gè)例子吧。

Hydra:好嘞,前面說過存入隊(duì)列的元素要實(shí)現(xiàn)Delayed接口,所以我們先定義這么一個(gè)類:

  1. public class Task implements Delayed { 
  2.     private String name
  3.     private long delay,expire; 
  4.     public Task(String name, long delay) { 
  5.         this.name = name
  6.         this.delay = delay; 
  7.         this.expire=System.currentTimeMillis()+delay; 
  8.     } 
  9.  
  10.     @Override 
  11.     public long getDelay(TimeUnit unit) { 
  12.         return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 
  13.     } 
  14.     @Override 
  15.     public int compareTo(Delayed o) { 
  16.         return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); 
  17.     } 
  18.  

實(shí)現(xiàn)了Delayed接口的類必須要實(shí)現(xiàn)下面的兩個(gè)方法:

  • getDelay方法用于計(jì)算對(duì)象的剩余延遲時(shí)間,判斷對(duì)象是否到期,計(jì)算方法一般使用過期時(shí)間減當(dāng)前時(shí)間。如果是0或負(fù)數(shù),表示延遲時(shí)間已經(jīng)用完,否則說明還沒有到期
  • compareTo方法用于延遲隊(duì)列的內(nèi)部排序比較,這里使用當(dāng)前對(duì)象的延遲時(shí)間減去被比較對(duì)象的延遲時(shí)間

在完成隊(duì)列中元素的定義后,向隊(duì)列中加入5個(gè)不同延遲時(shí)間的對(duì)象,并等待從隊(duì)列中取出:

  1. public void delay() throws InterruptedException { 
  2.     DelayQueue<Task> queue=new DelayQueue<>(); 
  3.     queue.offer(new Task("task1",5000)); 
  4.     queue.offer(new Task("task2",1000)); 
  5.     queue.offer(new Task("task3",6000)); 
  6.     queue.offer(new Task("task4",100)); 
  7.     queue.offer(new Task("task5",3000)); 
  8.  
  9.     while(true){ 
  10.         Task task = queue.take(); 
  11.         System.out.println(task); 
  12.     } 

運(yùn)行結(jié)果如下,可以看到按照延遲時(shí)間從短到長(zhǎng)的順序,元素被依次從隊(duì)列中取出。

  1. Task{name='task4', delay=100} 
  2. Task{name='task2', delay=1000} 
  3. Task{name='task5', delay=3000} 
  4. Task{name='task1', delay=5000} 
  5. Task{name='task3', delay=6000} 

面試官:看起來應(yīng)用還是挺簡(jiǎn)單的,但今天也不能這么草草了事吧,還是說說原理吧。

Hydra:開始的時(shí)候你自己不都說了嗎,今天咱們聊的DelayQueue和前幾天聊過的PriorityBlockingQueue多少有點(diǎn)關(guān)系。DelayQueue的底層是PriorityQueue,而PriorityBlockingQueue和它的差別也沒有多少,只是在PriorityQueue的基礎(chǔ)上加上鎖和條件等待,入隊(duì)和出隊(duì)用的都是二叉堆的那一套邏輯。底層使用的有這些:

  1. private final transient ReentrantLock lock = new ReentrantLock(); 
  2. private final PriorityQueue<E> q = new PriorityQueue<E>(); 
  3. private Thread leader = null
  4. private final Condition available = lock.newCondition(); 

面試官:你這樣也有點(diǎn)太糊弄我了吧,這就把我敷衍過去了?

Hydra:還沒完呢,還是先看入隊(duì)的offer方法,它的源碼如下:

  1. public boolean offer(E e) { 
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lock(); 
  4.     try { 
  5.         q.offer(e); 
  6.         if (q.peek() == e) { 
  7.             leader = null
  8.             available.signal(); 
  9.         } 
  10.         return true
  11.     } finally { 
  12.         lock.unlock(); 
  13.     } 

DelayQueue每次向優(yōu)先級(jí)隊(duì)列PriorityQueue中添加元素時(shí),會(huì)以元素的剩余延遲時(shí)間delay作為排序的因素,來實(shí)現(xiàn)使最先過期的元素排在隊(duì)首,以此達(dá)到在之后從隊(duì)列中取出的元素都是先取出最先到達(dá)過期的元素。

二叉堆的構(gòu)造過程我們上次講過了,就不再重復(fù)了。向隊(duì)列中添加完5個(gè)元素后,二叉堆和隊(duì)列中的結(jié)構(gòu)是這樣的:

當(dāng)每個(gè)元素在按照二叉堆的順序插入隊(duì)列后,會(huì)查看堆頂元素是否剛插入的元素,如果是的話那么設(shè)置leader線程為空,并喚醒在available上阻塞的線程。

這里先簡(jiǎn)單的介紹一下leader線程的作用,leader是等待獲取元素的線程,它的作用主要是用于減少不必要的等待,具體的使用在后面介紹take方法的時(shí)候我們細(xì)說。

面試官:也別一會(huì)了,趁熱打鐵直接講隊(duì)列的出隊(duì)方法吧。

Hydra:這還真沒法著急,在看阻塞方法take前還得先看看非阻塞的poll方法是如何實(shí)現(xiàn)的:

  1. public E poll() { 
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lock(); 
  4.     try { 
  5.         E first = q.peek(); 
  6.         if (first == null || first.getDelay(NANOSECONDS) > 0) 
  7.             return null
  8.         else 
  9.             return q.poll(); 
  10.     } finally { 
  11.         lock.unlock(); 
  12.     } 

代碼非常短,理解起來非常簡(jiǎn)單,在加鎖后首先檢查堆頂元素,如果堆頂元素為空或沒有到期,那么直接返回空,否則返回堆頂元素,然后解鎖。

面試官:好了,鋪墊完了吧,該講阻塞方法的過程了吧?

Hydra:阻塞的take方法理解起來會(huì)比上面稍微困難一點(diǎn),我們還是直接看它的源碼:

  1. public E take() throws InterruptedException { 
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lockInterruptibly(); 
  4.     try { 
  5.         for (;;) { 
  6.             E first = q.peek(); 
  7.             if (first == null
  8.                 available.await(); 
  9.             else { 
  10.                 long delay = first.getDelay(NANOSECONDS); 
  11.                 if (delay <= 0) 
  12.                     return q.poll(); 
  13.                 first = null; // don't retain ref while waiting 
  14.                 if (leader != null
  15.                     available.await(); 
  16.                 else { 
  17.                     Thread thisThread = Thread.currentThread(); 
  18.                     leader = thisThread; 
  19.                     try { 
  20.                         available.awaitNanos(delay); 
  21.                     } finally { 
  22.                         if (leader == thisThread) 
  23.                             leader = null
  24.                     } 
  25.                 } 
  26.             } 
  27.         } 
  28.     } finally { 
  29.         if (leader == null && q.peek() != null
  30.             available.signal(); 
  31.         lock.unlock(); 
  32.     } 

阻塞過程中分支條件比較復(fù)雜,我們一個(gè)一個(gè)看:

  • 首先獲取堆頂元素,如果為空,那么說明隊(duì)列中還沒有元素,讓當(dāng)前線程在available上進(jìn)行阻塞等待
  • 如果堆頂元素不為空,那么查看它的過期時(shí)間,如果已到期,那么直接彈出堆頂元素
  • 如果堆頂元素還沒有到期,那么查看leader線程是否為空,如果leader線程不為空的話,表示已經(jīng)有其他線程在等待獲取隊(duì)列的元素,直接阻塞當(dāng)前線程。
  • 如果leader為空,那么把當(dāng)前線程賦值給它,并調(diào)用awaitNanos方法,在阻塞delay時(shí)間后自動(dòng)醒來。喚醒后,如果leader還是當(dāng)前線程那么把它置為空,重新進(jìn)入循環(huán),再次判斷堆頂元素是否到期。

當(dāng)有隊(duì)列中的元素完成出隊(duì)后,如果leader線程為空,并且堆中還有元素,就喚醒阻塞在available上的其他線程,并釋放持有的鎖。

面試官:我注意到一個(gè)問題,在上面的代碼中,為什么要設(shè)置first = null呢?

Hydra:假設(shè)有多個(gè)線程在執(zhí)行take方法,當(dāng)?shù)谝粋€(gè)線程進(jìn)入時(shí),堆頂元素還沒有到期,那么會(huì)將leader指向自己,然后阻塞自己一段時(shí)間。如果在這期間有其他線程到達(dá),會(huì)因?yàn)閘eader不為空阻塞自己。

當(dāng)?shù)谝粋€(gè)線程阻塞結(jié)束后,如果將堆頂元素彈出成功,那么first指向的元素應(yīng)該被gc回收掉。但是如果還被其他線程持有的話,它就不會(huì)被回收掉,所以將first置為空可以幫助完成垃圾回收。

面試官:我突然有一個(gè)發(fā)散性的疑問,定時(shí)任務(wù)線程池ScheduledThreadPoolExecutor,底層使用的也是DelayQueue嗎?

Hydra:問題很不錯(cuò),但很遺憾并不是,ScheduledThreadPoolExecutor在類中自己定義了一個(gè)DelayedWorkQueue內(nèi)部類,并沒有直接使用DelayQueue。不過如果你看一下源碼,就會(huì)看到它們實(shí)現(xiàn)的邏輯基本一致,同樣是基于二叉堆的上浮、下沉、擴(kuò)容,也同樣基于leader、鎖、條件等待等操作,只不過自己用數(shù)組又實(shí)現(xiàn)了一遍而已。說白了,看看兩個(gè)類的作者,都是Doug Lea大神,所以差異根本沒有多大。

面試官:好了,今天先到這吧,能最后再總結(jié)一下嗎?

Hydra:DelayQueue整體理解起來也沒有什么困難的點(diǎn),難的地方在前面聊優(yōu)先級(jí)隊(duì)列的時(shí)候基本已經(jīng)掃清了,新加的東西也就是一個(gè)對(duì)于leader線程的操作,使用了leader線程來減少不必要的線程等待時(shí)間。

面試官:今天的面試有點(diǎn)短啊,總是有點(diǎn)意猶未盡的感覺,看來下次得給你加點(diǎn)料了。

Hydra:…

 

責(zé)任編輯:姜華 來源: 碼農(nóng)參上
相關(guān)推薦

2021-05-17 07:36:54

ArrayBlocki面試集合

2021-05-23 16:03:42

LinkedBlock面試阻塞隊(duì)列

2021-05-29 12:24:29

Synchronous公平模式

2021-06-02 21:31:39

Synchronous非公平模式

2021-11-02 10:43:34

Java面試安全

2021-01-18 10:48:51

DockerRedisMySQL

2012-08-14 10:31:28

面試

2012-08-21 09:20:57

Yahoo

2012-08-09 10:02:08

面試Google

2012-11-05 10:01:32

2021-10-11 19:54:04

JVM面試虛擬機(jī)

2016-12-20 18:21:29

Hadoop大數(shù)據(jù)面試

2009-03-03 09:33:13

面試ORACLE

2018-08-21 13:25:01

編程語言Java面試題

2021-12-09 07:13:25

C#集合類型

2010-12-29 10:33:51

Oracle

2020-07-28 08:59:22

JavahreadLocal面試

2018-04-19 14:11:50

2018-07-10 16:50:28

數(shù)據(jù)庫MySQL面試題

2012-10-15 16:14:54

2012年度IT博客大鄧侃
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)