DelayQueue的实现原理
DelayQueue的本质是一个实现了针对元素为Delayed的PriorityQueue, 它里面比较出彩的地方就是使用了Leader/Follower 模式,来减少线程为是否过期轮询,这样提高了系统效率。
public interface Delayed extends Comparable<Delayed> {
/**
* Returns the remaining delay associated with this object, in the
* given time unit.
*
* @param unit the time unit
* @return the remaining delay; zero or negative values indicate
* that the delay has already elapsed
*/
long getDelay(TimeUnit unit);
}
主要代码剖析:
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>();
/**
* Thread designated to wait for the element at the head of
* the queue. This variant of the Leader-Follower pattern
* (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to
* minimize unnecessary timed waiting. When a thread becomes
* the leader, it waits only for the next delay to elapse, but
* other threads await indefinitely. The leader thread must
* signal some other thread before returning from take() or
* poll(...), unless some other thread becomes leader in the
* interim. Whenever the head of the queue is replaced with
* an element with an earlier expiration time, the leader
* field is invalidated by being reset to null, and some
* waiting thread, but not necessarily the current leader, is
* signalled. So waiting threads must be prepared to acquire
* and lose leadership while waiting.
*/
private Thread leader = null;
上面代码指出了内部实现,一个优先级队列,一个并发锁,还有个LEADER/FOLLOWER的head;
Leader/Follower 模式 参看 http://www.cs.wustl.edu/~schmidt/POSA/POSA2/
offer 方法:
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
take 方法:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
最重要的是这里:
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
为什么不是直接
available.awaitNanos(delay);
每个awaitNanos 就像给每个线程挂一个倒计时闹钟一样,要是直接用这个方式的话,那么每个需要等待的线程就需要挂一个倒计时的闹钟,这样的话,效率就变低了很多。
所以能理解这里为什么使用leader / follower 模式提高效率了。
DelayQueue的常用方法
public void put(E e)
public E take() throws InterruptedException
public boolean offer(E e)
public E poll()
public int drainTo(Collection<? super E> c)
DelayQueue的应用案例
该场景来自于http://ideasforjava.iteye.com/blog/657384,模拟一个考试的日子,考试时间为120分钟,30分钟后才可交卷,当时间到了,或学生都交完卷了考试结束。
这个场景中几个点需要注意:
- 考试时间为120分钟,30分钟后才可交卷,初始化考生完成试卷时间最小应为30分钟
- 对于能够在120分钟内交卷的考生,如何实现这些考生交卷
- 对于120分钟内没有完成考试的考生,在120分钟考试时间到后需要让他们强制交卷
package test.time;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class Examinee implements Delayed {
private int id;
private String name;
private long submitTime;
public Examinee(int i){
this.id = i;
this.name = "考生-" + i;
this.submitTime = Exam.EXAM_DEADLINE;
}
public int getId(){
return id;
}
/**
* 交卷了哦。
*/
public void submit(){
long current = System.currentTimeMillis();
if(current >= Exam.EXAM_MIN_TIME){
submitTime = current;
}else{
long cost = TimeUnit.SECONDS.convert(current - Exam.EXAM_START, TimeUnit.MILLISECONDS);
System.err.println("考试时间没有超过30分钟, ["+name+"] 不能交卷, 考试用时: "+cost);
}
}
@Override
public int compareTo(Delayed o) {
return (int)(getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public long getDelay(TimeUnit unit) {
long delayMills = submitTime - System.currentTimeMillis();
return unit.convert(delayMills, TimeUnit.MILLISECONDS);
}
public String toString(){
long current = System.currentTimeMillis();
long cost = TimeUnit.SECONDS.convert(current - Exam.EXAM_START, TimeUnit.MILLISECONDS);
return name+" 在已经提交试卷, 考试耗时: "+cost;
}
}
package test.time;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
public class Exam {
// 考试开始时间
static long EXAM_START = System.currentTimeMillis();
// 考试时间 120 秒 (为了方便模拟)
static long EXAM_DEADLINE = EXAM_START + 120*1000L;
// 最早交卷时间 30 秒
static long EXAM_MIN_TIME = EXAM_START + 30*1000L;
// 考场总人数
private static final int EXAMINEE_NUM = 20;
private DelayQueue<Examinee> examinees = new DelayQueue<>();
private Map<Integer, Examinee> map = new ConcurrentHashMap<>(EXAMINEE_NUM*3/2);
private int submitCount;
public Exam(){
for(int i=1; i<=EXAMINEE_NUM; i++){
Examinee e = new Examinee(i);
examinees.offer(e);
map.put(i, e);
}
}
public void examining() throws InterruptedException{
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date start = new Date(EXAM_START);
Date end = new Date(EXAM_DEADLINE);
System.out.println("====> 考试开始 ["+format.format(start)+"] ...");
// 25 分钟以后
TimeUnit.SECONDS.sleep(25);
while(System.currentTimeMillis() <= EXAM_DEADLINE ){
someoneSubmit();
TimeUnit.SECONDS.sleep(2);
System.out.println("--------------- time past 2 seconds -------");
List<Examinee> submitted = new LinkedList<>();
examinees.drainTo(submitted);
for(Examinee e: submitted){
System.out.println(e);
}
submitCount += submitted.size();
if(submitCount >= EXAMINEE_NUM){
break;
}
}
System.out.println("====> 考试结束 ["+format.format(end)+"] ...");
}
void someoneSubmit(){
int num = new Random().nextInt(EXAMINEE_NUM)+1;
map.get(num).submit();
}
public static void main(String[] args){
try {
new Exam().examining();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}