DelayQueue 分析

DelayQueue 分析

DelayQueue队列是支持延迟队列,在里面接口必须是实现Delayed接口。下面先看看Delayed接口

1. Delayed 接口分析

Delayed接口实现了Comparable接口,并且提供了getDelay方法,支持返回一个数字,这个数字表示这个对象剩余的时间,在给定的单位里面,0或者负数意味着这个对象已经到期,直接返回,

也就是说,一个对象实现了这个接口,就必须要实现两个接口,一个是getDelay一个是Comparable


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);
}

2. 属性分析

从这里可以发现,DelayQueue的实现是依托于PriorityQueue。建议看看PriorityQueue分析,除了一个锁和一个Conditio之外,leader是最引人注目的。

leader表示线程等待队列的头元素,他是Leader-Follower的变种实现,当一个thread变为leader的时候,提供了一个最小化的非必要的时间等待。只有在有元素delay到时间之后,这个线程才会才会变为leader,其他的线程都是在等待。leader的线程必须唤醒一个follower,在take或者poll之前,


    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();

     // 这里用到了Leader-Follower的模式。关于这个模式在后面的章节里面讲解
    
    private Thread leader = null;

    /**
     * Condition signalled when a newer element becomes available
     * at the head of the queue or a new thread may need to
     * become leader.
     */
    private final Condition available = lock.newCondition();

3. 主要方法分析

在这里插入图片描述

只要看实现了BlockingQueue,那就说明他是一个线程安全的。

1. put 方法

put方法里面调用的还是offer方法,并且put方法里面没有等待机制。

因为DelayQueue是依托于PriorityQueue来实现的。关于PriorityQueue入队的操作在这里就不说了。

问题?

  1. offer里面的唤醒操作是出于什么样的目的

    可以看到,如果当前放进去的元素是最小的(堆头元素),那就说明当前的这个元素要消费了,

    有下面两个方面

    1. 如果一开始堆中没有元素,take线程会一直等待,put一个进来说明,要唤醒take线程来操作了
    2. 放进去的元素是堆中最小的元素,把leader变为null,唤醒follower,表示之前的leader等待的元素不是最新的了,就需要放弃掉,leader变为null,唤醒follower,follower就会来获取锁,发现没有leader,自己变为leader,之前等待的leader就没有用了。因为`不是最新的数据了
// put方法调用了offer方法
public void put(E e) {
        offer(e);
    }
 
// 他的底层还是用的是PriorityQueue的方法,关于PriorityQueue的博客在之前已经说清楚了。
  public boolean offer(E e) {
      // 先上锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
           //入队
            q.offer(e);
           // 如果头节点就是当前要放入的节点,那么这个说明,当前放进去的节点就是整个堆中最小的节点。因为PriorityQueue里面是按照compareTo 方法来做判断的。Comparator来判断
            if (q.peek() == e) {
               // leader变为null
                leader = null;
               // 现在的leader要唤醒一个Follower作为leader
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

2. take方法

元素的出堆这里就不说了。重点看看这里的Leader和Follower的工作和选举的过程。

一开始,先拿到堆头元素,如果没有元素,都等待。这个时候如果put线程放了一个元素,根据上面的逻辑,堆头的元素就是put线程放的元素,put线程,唤醒available,。take线程就继续让下走。

拿到堆头的元素,堆头的元素是由compareable接口决定的。从这里就可以看出来,getDelaycompareTo方法是不一样的功能。

如果已经到期了,直接出队,否则就需要等待。

如果没有leader,那么当前线程就是leader。根据delay的时间来等待,注意,只有leader,才能超时时间等待,别的都是一直等待。在等待完成(处理完任务)之后,就leader变为null。再次循环,再次判断。这就有问题出现了

问题

  1. 在发现没有leader,当前线程变为leader之后,在等待的时间段,别的线程拿到锁进来,会不会处理。

    不会,这个时候leader不为null,拿到锁的线程进来之后,看到leader不是null,就直接等待了。

  2. 假如说,等待时间到了之后,当前线程(A)再次获取锁的时候,有一个线程(B)也正要获取锁。这个时候还正好被B线程获取锁,拿到堆头元素,发现没有leader,这个之后堆头元素还是之前A线程等待的堆头元素,(中间没有线程操作,唯一的操作是在A。B线程获取锁的时候)。B发现堆头元素已经到期了,直接出堆,这个时候A线程就做了 一场无用功

在堆头元素需要出堆之后,还有一个finally来操作,如果没有leader并且堆头还有值,就唤醒Follower。让Follower变为leader。继续操作上面的流程。

   public E take() throws InterruptedException {
       // 先上锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
               // 拿到队头元素,队头元素是整个堆中最小的,注意,这里是peek,不需要出队
                E first = q.peek();
                // 如果fist是null,说明就没有元素,那当前线程就只能等待了
                if (first == null)
                    available.await();
                else {
                   // 拿到delay时间,要清楚,Comparable和Delayed是不一样的作用,
                   // Comparable是用于在 PriorityQueue 排序,
                  // Delayed 是用来做等待
                    long delay = first.getDelay(NANOSECONDS);
                   // 如果是delay到期,就直接出栈。
                    if (delay <= 0)
                        return q.poll();
                   
                  
                    // 等待的时候不需要保留引用关系,
                   // 断开了first对堆头的引用关系,
                    first = null; 
                   //这个时候说明还是有leader的,有leader,Follower就等待。
                    if (leader != null)
                        available.await();
                    else {
                       // 如果没有leader,当前线程就是leader。
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                           // 然后当前线程(leader)等待delay时间。
                           // 这里其实就是真正干活的节点,在Leader-Follower模型里面。
                            available.awaitNanos(delay);
                        } finally {
                           // 在此判断,数据是否一致,如果一致就将leader变为null,在这次操作之后,好让Follower抢锁。
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            //如果leader没有了,并且队列中还有值,就唤醒一个Follower。用来做Leader
            if (leader == null && q.peek() != null)
                available.signal();
           // 释放锁
            lock.unlock();
        }
    }

Leader-Follower

可以参考这篇论文© Douglas C. Schmidt 1998 - 2000, all rights reserved, © Siemens AG 1998 - 2000, all rights reserved
19.06.2000 lf.doc
Leader/Followers

下面所说的通用的Leader-Follower的模型。

并发编程模型中的一种。主要有两个角色

  1. Leader

    leader线程等待事件发生,处理事件并且从Following中选举一个新的Leader。在处理完任务后,重新添加到Follower

  2. Follower

    等待变为Leader

    下面是一个线程(不管是Leader还是Follower)的不同状态图。

在这里插入图片描述

在通用的模型里面,leader和Following是一组可以复用的线程,只对事件感兴趣,在DelayQueue里面,take操作就是事件源。

下面的UML图表示了大体的一个基本结构

在这里插入图片描述

在这里插入图片描述

可以看到,leader和Follower都是在线程池总维护的可复用的线程,并且他们本身没有区别。

一开始发现,Thread1先来,变为了leader,thread2后来变为了follower。

当事件发生(HandleSet中有操作了)Leader线程在Follower中选举一个新的Leader(promote_new_leader),thread2变为了新的leader。thread1去处理事件(handle_event)处理完之后,最后变为了follower。

大体就是这个样子了,详细的可以看上面说的论文里面的内容,里面阐述了现在OLTP中的问题,和解决方案。

DelayQueue中,这种模式的变种实现。

根据 getDelay方法返回值,等待的操作是处理事件的过程。

如果将put和take想象为服务器接受请求和处理请求的两个操作。put就是事件。take就是handle。

不需要堵塞的时候是不需要这种模式的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值