死磕 java集合之DelayQueue源码分析

问题

(1)DelayQueue是阻塞队列吗?

(2)DelayQueue的实现方式?

(3)DelayQueue主要用于什么场景?

简介

DelayQueue是java并发包下的延时阻塞队列,常用于实现定时任务。

继承体系

从继承体系可以看到,DelayQueue实现了BlockingQueue,所以它是一个阻塞队列。

另外,DelayQueue还组合了一个叫做Delayed的接口,DelayQueue中存储的所有元素必须实现Delayed接口。

那么,Delayed是什么呢?

publicinterfaceDelayedextendsComparable{longgetDelay(TimeUnit unit);}

Delayed是一个继承自Comparable的接口,并且定义了一个getDelay()方法,用于表示还有多少时间到期,到期了应返回小于等于0的数值。

源码分析

主要属性

// 用于控制并发的锁privatefinaltransientReentrantLock lock =newReentrantLock();// 优先级队列privatefinalPriorityQueue q =newPriorityQueue();// 用于标记当前是否有线程在排队(仅用于取元素时)privateThread leader =null;// 条件,用于表示现在是否有可取的元素privatefinalCondition available = lock.newCondition();

从属性我们可以知道,延时队列主要使用优先级队列来实现,并辅以重入锁和条件来控制并发安全。

因为优先级队列是无界的,所以这里只需要一个条件就可以了。

还记得优先级队列吗?点击链接直达【死磕 java集合之PriorityQueue源码分析

主要构造方法

publicDelayQueue(){}publicDelayQueue(Collection<? extends E> c){this.addAll(c);}

构造方法比较简单,一个默认构造方法,一个初始化添加集合c中所有元素的构造方法。

入队

因为DelayQueue是阻塞队列,且优先级队列是无界的,所以入队不会阻塞不会超时,因此它的四个入队方法是一样的。

publicbooleanadd(E e){returnoffer(e);}publicvoidput(E e){    offer(e);}publicbooleanoffer(E e,longtimeout, TimeUnit unit){returnoffer(e);}publicbooleanoffer(E e){finalReentrantLock lock =this.lock;    lock.lock();try{        q.offer(e);if(q.peek() == e) {            leader =null;            available.signal();        }returntrue;    }finally{        lock.unlock();    }}

入队方法比较简单:

(1)加锁;

(2)添加元素到优先级队列中;

(3)如果添加的元素是堆顶元素,就把leader置为空,并唤醒等待在条件available上的线程;

(4)解锁;

出队

因为DelayQueue是阻塞队列,所以它的出队有四个不同的方法,有抛出异常的,有阻塞的,有不阻塞的,有超时的。

我们这里主要分析两个,poll()和take()方法。

publicEpoll(){finalReentrantLock lock =this.lock;    lock.lock();try{        E first = q.peek();if(first ==null|| first.getDelay(NANOSECONDS) >0)returnnull;elsereturnq.poll();    }finally{        lock.unlock();    }}

poll()方法比较简单:

(1)加锁;

(2)检查第一个元素,如果为空或者还没到期,就返回null;

(3)如果第一个元素到期了就调用优先级队列的poll()弹出第一个元素;

(4)解锁。

publicEtake()throwsInterruptedException{finalReentrantLock lock =this.lock;    lock.lockInterruptibly();try{for(;;) {// 堆顶元素E first = q.peek();// 如果堆顶元素为空,说明队列中还没有元素,直接阻塞等待if(first ==null)                available.await();else{// 堆顶元素的到期时间longdelay = first.getDelay(NANOSECONDS);// 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素if(delay <=0)returnq.poll();// 如果delay大于0 ,则下面要阻塞了// 将first置为空方便gc,因为有可能其它元素弹出了这个元素// 这里还持有着引用不会被清理first =null;// don't retain ref while waiting// 如果前面有其它线程在等待,直接进入等待if(leader !=null)                    available.await();else{// 如果leader为null,把当前线程赋值给它Thread thisThread = Thread.currentThread();                    leader = thisThread;try{// 等待delay时间后自动醒过来// 醒过来后把leader置空并重新进入循环判断堆顶元素是否到期// 这里即使醒过来后也不一定能获取到元素// 因为有可能其它线程先一步获取了锁并弹出了堆顶元素// 条件锁的唤醒分成两步,先从Condition的队列里出队// 再入队到AQS的队列中,当其它线程调用LockSupport.unpark(t)的时候才会真正唤醒// 关于AQS我们后面会讲的^^available.awaitNanos(delay);                    }finally{// 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素if(leader == thisThread)                            leader =null;                    }                }            }        }    }finally{// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程if(leader ==null&& q.peek() !=null)// signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒available.signal();// 解锁,这才是真正的唤醒lock.unlock();    }}

take()方法稍微要复杂一些:

(1)加锁;

(2)判断堆顶元素是否为空,为空的话直接阻塞等待;

(3)判断堆顶元素是否到期,到期了直接调用优先级队列的poll()弹出元素;

(4)没到期,再判断前面是否有其它线程在等待,有则直接等待;

(5)前面没有其它线程在等待,则把自己当作第一个线程等待delay时间后唤醒,再尝试获取元素;

(6)获取到元素之后再唤醒下一个等待的线程;

(7)解锁;

使用方法

说了那么多,是不是还是不知道怎么用呢?那怎么能行,请看下面的案例:

publicclassDelayQueueTest{publicstaticvoidmain(String[] args){        DelayQueue queue =newDelayQueue<>();longnow = System.currentTimeMillis();// 启动一个线程从队列中取元素newThread(()->{while(true) {try{// 将依次打印1000,2000,5000,7000,8000System.out.println(queue.take().deadline - now);                }catch(InterruptedException e) {                    e.printStackTrace();                }            }        }).start();// 添加5个元素到队列中queue.add(newMessage(now +5000));        queue.add(newMessage(now +8000));        queue.add(newMessage(now +2000));        queue.add(newMessage(now +1000));        queue.add(newMessage(now +7000));    }}classMessageimplementsDelayed{longdeadline;publicMessage(longdeadline){this.deadline = deadline;    }@OverridepubliclonggetDelay(TimeUnit unit){returndeadline - System.currentTimeMillis();    }@OverridepublicintcompareTo(Delayed o){return(int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));    }@OverridepublicStringtoString(){returnString.valueOf(deadline);    }}

是不是很简单,越早到期的元素越先出队。

总结

(1)DelayQueue是阻塞队列;

(2)DelayQueue内部存储结构使用优先级队列;

(3)DelayQueue使用重入锁和条件来控制并发安全;

(4)DelayQueue常用于定时任务;

写在最后:

码字不易看到最后了,那就点个关注呗,只收藏不点关注的都是在耍流氓!关注并私信我“架构”,免费送一些Java架构资料,先到先得!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 引言 JDK中除了上文提到的各种并发容器,还提供了丰富的阻塞队列。阻塞队列统一实现了BlockingQueue接口...
    小刀爱编程阅读 558评论 0 0
  • DelayQueue源码学习 DelayQueue是一个提供过期时间的队列,只返回消耗完等待时间的元素,暂时还没发...
    senninha阅读 442评论 0 0
  • 本文将会对DelayQueue做一个简单的介绍,并提供部分源码的分析。 DelayQueue的特性基本上由Bloc...
    逍遥jc阅读 1,414评论 2 2
  • 第一次地面课。体验还不错,老师讲得很吸引人,各位。。。嗯。。姐姐们ಡ ﹏ ಡ也很友善。小小一间屋子里能人不少,幸会...
    牧狼人布川酷阅读 144评论 0 0
  • 在儿子的幼儿园母校我们养成了爱读书爱阅读的好习惯,那个时候有绘本漂流活动,每天都可以从幼儿园带回来孩子从未看过的书...
    野地百合_35b6阅读 361评论 1 2