DelayQueue是一個支持延時獲取元素的無界阻塞隊列。里面的元素全部都是“可延期”的元素,列頭的元素是最先“到期”的元素,如果隊列里面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行。也就是說只有在延遲期到時才能夠從隊列中取元素。

DelayQueue主要用於兩個方面:

- 緩存:清掉緩存中超時的緩存數據

- 任務超時處理

DelayQueue

DelayQueue實現的關鍵主要有如下幾個:

可重入鎖ReentrantLock

用於阻塞和通知的Condition對象

根據Delay時間排序的優先級隊列:PriorityQueue

用於優化阻塞通知的線程元素leader

ReentrantLock、Condition這兩個對象就不需要闡述了,他是實現整個BlockingQueue的核心。PriorityQueue是一個支持優先級線程排序的隊列(參考【死磕Java並發】—–J.U.C之阻塞隊列:PriorityBlockingQueue),leader后面闡述。這里我們先來了解Delay,他是實現延時操作的關鍵。

Delayed

Delayed接口是用來標記那些應該在給定延遲時間之后執行的對象,它定義了一個long getDelay(TimeUnit unit)方法,該方法返回與此對象相關的的剩余時間。同時實現該接口的對象必須定義一個compareTo 方法,該方法提供與此接口的 getDelay 方法一致的排序。

public interface Delayed extends Comparable {

long getDelay(TimeUnit unit);

}

如何使用該接口呢?上面說的非常清楚了,實現該接口的getDelay()方法,同時定義compareTo()方法即可。

內部結構

先看DelayQueue的定義:

public class DelayQueue extends AbstractQueue

implements BlockingQueue {

/** 可重入鎖 */

private final transient ReentrantLock lock = new ReentrantLock();

/** 支持優先級的BlockingQueue */

private final PriorityQueue q = new PriorityQueue();

/** 用於優化阻塞 */

private Thread leader = null;

/** Condition */

private final Condition available = lock.newCondition();

/**

* 省略很多代碼

*/

}

看了DelayQueue的內部結構就對上面幾個關鍵點一目了然了,但是這里有一點需要注意,DelayQueue的元素都必須繼承Delayed接口。同時也可以從這里初步理清楚DelayQueue內部實現的機制了:以支持優先級無界隊列的PriorityQueue作為一個容器,容器里面的元素都應該實現Delayed接口,在每次往優先級隊列中添加元素時以元素的過期時間作為排序條件,最先過期的元素放在優先級最高。

offer()

public boolean offer(E e) {

final ReentrantLock lock = this.lock;

lock.lock();

try {

// 向 PriorityQueue中插入元素

q.offer(e);

// 如果當前元素的對首元素(優先級最高),leader設置為空,喚醒所有等待線程

if (q.peek() == e) {

leader = null;

available.signal();

}

// 無界隊列,永遠返回true

return true;

} finally {

lock.unlock();

}

}

offer(E e)就是往PriorityQueue中添加元素,具體可以參考(【死磕Java並發】—–J.U.C之阻塞隊列:PriorityBlockingQueue)。整個過程還是比較簡單,但是在判斷當前元素是否為對首元素,如果是的話則設置leader=null,這是非常關鍵的一個步驟,后面闡述。

take()

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

for (;;) {

// 對首元素

E first = q.peek();

// 對首為空,阻塞,等待off()操作喚醒

if (first == null)

available.await();

else {

// 獲取對首元素的超時時間

long delay = first.getDelay(NANOSECONDS);

// <=0 表示已過期,出對,return

if (delay <= 0)

return q.poll();

first = null; // don't retain ref while waiting

// leader != null 證明有其他線程在操作,阻塞

if (leader != null)

available.await();

else {

// 否則將leader 設置為當前線程,獨占

Thread thisThread = Thread.currentThread();

leader = thisThread;

try {

// 超時阻塞

available.awaitNanos(delay);

} finally {

// 釋放leader

if (leader == thisThread)

leader = null;

}

}

}

}

} finally {

// 喚醒阻塞線程

if (leader == null && q.peek() != null)

available.signal();

lock.unlock();

}

}

首先是獲取對首元素,如果對首元素的延時時間 delay <= 0 ,則可以出對了,直接return即可。否則設置first = null,這里設置為null的主要目的是為了避免內存泄漏。如果 leader != null 則表示當前有線程占用,則阻塞,否則設置leader為當前線程,然后調用awaitNanos()方法超時等待。

first = null

這里為什么如果不設置first = null,則會引起內存泄漏呢?線程A到達,列首元素沒有到期,設置leader = 線程A,這是線程B來了因為leader != null,則會阻塞,線程C一樣。假如線程阻塞完畢了,獲取列首元素成功,出列。這個時候列首元素應該會被回收掉,但是問題是它還被線程B、線程C持有着,所以不會回收,這里只有兩個線程,如果有線程D、線程E…呢?這樣會無限期的不能回收,就會造成內存泄漏。

這個入隊、出對過程和其他的阻塞隊列沒有很大區別,無非是在出對的時候增加了一個到期時間的判斷。同時通過leader來減少不必要阻塞。

歡迎掃一掃我的公眾號關注 — 及時得到博客訂閱哦!

–— Java成神之路: 488391811(一起走向Java成神) –—

java加载c库阻塞_【死磕Java並發】-----J.U.C之阻塞隊列:DelayQueue相关推荐

  1. java 原子类能做什么_死磕 java原子类之终结篇(面试题)

    概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...

  2. java 同步锁_死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  3. java任务流程_死磕 java线程系列之线程池深入解析——普通任务执行流程

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了Java中 ...

  4. java加载并运行虚拟机_《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?...

    Java虚拟机是如何加载Java类的?  这个问题也就是面试常问到的Java类加载机制.在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样. ...

  5. java 加载class文件路径_动手实现MVC: 1. Java 扫描并加载包路径下class文件

    背景 用过spring框架之后,有个指定扫描包路径,然后自动实例化一些bean,这个过程还是比较有意思的,抽象一下,即下面三个点 如何扫描包路径下所有的class文件 如何扫描jar包中对应包路径下所 ...

  6. java 加载dll后打包_让Jacob从当前路径读取dll文件及相关打包方法

    让Jacob从当前路径读取dll文件及相关打包方法 独立观察员2013.08.12 Jacob  LibraryLoader.class修改版代码 功能:让jacob可在当前路径下的dll文件夹内读取 ...

  7. java 手编线程池_死磕 java线程系列之自己动手写一个线程池

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...

  8. 2019死磕java面试题_死磕 java同步系列之开篇

    简介 同步系列,这是彤哥想了好久的名字,本来是准备写锁相关的内容,但是java中的CountDownLatch.Semaphore.CyclicBarrier这些类又不属于锁,它们和锁又有很多共同点, ...

  9. java ee是什么_死磕 java集合之HashSet源码分析

    问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...

最新文章

  1. 计算机网络技术问题解决,计算机网络故障常见问题汇总,掌握了这些,你离女神又会更进一步...
  2. 系统分析的几个好工具
  3. anaconda安装好tensorflow后,无法在jupyter notebook上使用的解决方法
  4. pytorch之深度学习
  5. cad文件格式(dwg、dxf、dwf、dws等)转其他格式(svg、,tiff、jpej、png、xml、pdf等)的四种方式(java)
  6. Linux CentOS 7修改主机名称
  7. CodeForces128A - Statues 解题报告
  8. python小于_删除python中小于某个值的行
  9. 阿里巴巴矢量图库开源http://www.iconfont.cn/collections/detail?cid=29
  10. 【深度】新派LaaS协议Elephant:重振DeFi赛道发展的关键
  11. Vim 增加man快捷方式
  12. 【电脑配置知识】显卡 GPU
  13. nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全
  14. 微信小程序如何进行反编译详细教程
  15. 关于数字出版物的版权
  16. HashMap是线程安全的吗?有什么线程安全的方法
  17. 计算机图形学算法总结
  18. Classification and inference with machine learning
  19. 银河系中心黑洞的第一张照片,本文带你了解发现的过程
  20. C语言学习(十一)之字符输入/输出

热门文章

  1. CSS3圆圈动画放大缩小循环动画效果
  2. CSS篇 《图解CSS3》笔记 Flex
  3. [转] GIS算法源码集合
  4. 删除不同粒度的事实表记录中重复的度量值数据的SQL语句
  5. 5.什么是二叉查找树?原理
  6. 天马行空W:在C++中调用DLL中的函数
  7. 野火STM32F103教学视频完整目录(配合霸道-指南者开发板)
  8. 接上一篇Ansible和celery的结合,在celery的tasks.py文件里为了实现并发不阻塞的需求,用到了多进程
  9. linux通过管道的进程通信,linux 线程或进程之间通过管道通信(pipe)
  10. docker pip 换源_Docker 部署 jupyterlab 3.0.3