DelayQueue 学习和应用


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分钟后才可交卷,当时间到了,或学生都交完卷了考试结束。

这个场景中几个点需要注意:

  1. 考试时间为120分钟,30分钟后才可交卷,初始化考生完成试卷时间最小应为30分钟
  2. 对于能够在120分钟内交卷的考生,如何实现这些考生交卷
  3. 对于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();
		}
	}

}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值