DelayQueue与Leader-Follwer

  •  技术特性

        DelayQueue是JDK并发包中提供的一个容器类,顾名思义这个队列提供了一种延迟机制。

  • 容器作用

        DelayQueue容器中存储的元素具有时效性,该容器需要和实现了Delayed接口的元素配合使用。Delayed类型的元素具有一个失效时长,即这个对象将在多长时间内过期。Delayed接口中提供了一个getDelay方法来获取这个时长,当返回的时长为0或为负数时表示这个对象已经过期了。Delayed接口还实现了一个Comparable接口,有助于它在容器中实现排序。

     代码片段 Delayed接口

public interface Delayed extends Comparable<Delayed> {
     /**
      * 返回该对象将要时效的时间,unit为时间单位。
      */
     long getDelay(TimeUnit unit);
}

 

 

  • 数据结构

        DelayQueue容器内部使用了一个PriorityQueue (队列中的元素具有一定的优先级)来实现的。

  • 容器性质

        只有在延迟期满时才能从中提取出元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列头部不会被返回,即队列的 poll 方法将返回null。

  • 容器边界

        容器是一个无界阻塞队列。但此队列不允许使用 null 元素。

  • 常用方法

        public int size()


        返回此 collection 中的元素数,包含已经到期的元素和未到期的元素。如果此容器包含的元素大于 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE。

  • 阻塞方法

        public E take()


        如果该队列的头部元素已经过期则立即返回,如果该队列的头部无法立刻得到一个过期的元素。否则该方法将一直等待,直到获得一个过期的元素。

        public E poll(long timeout, TimeUnit unit) throws InterruptedException


        如果该队列的头部元素已经过期则立即返回,如果该队列的头部无法立刻得到一个过期的元素。否则该方法会在指定的timeout时间内等待,直到得到一个过期的元素。如果在指定的timeout的时间到达后依然没有元素过期,则返回null。

  • 非阻塞方法

        public E poll()


        如果该队列的头部元素已经过期则立即返回,如果没有过期元素则立即返回null。该方法不会被阻塞。

        public boolean offer(E e, long timeout, TimeUnit unit)


        将指定的元素插入此延迟队列中。虽然该方法提供了timeout和util参数,会让人误以为这个方法会被阻塞。但由于该队列是无界的,所以此方法不会阻塞。


        DelayQueue类实现了BlockingQueue接口,这个方法由BlockingQueue接口得到,因此必须实现。该实现忽略了两个与超时相关的参数,而直接调用无参数的offer方法。

代码片段 DelayQueue的offer方法

public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}

                      
        public boolean remove(Object o)

 


        从该队列中删除指定的元素(如果存在),而无需关心这个元素是否有过期。

        public void clear()


        移除此延迟队列中的所有元素。该方法无需等待队列中的元素过期。DelayQueue的clear方法实际上是委托PriorityQueue的clear方法实现的,直接将队列中的元素置空。


代码片段 PriorityQueue的clear方法实现

public void clear() {
        modCount++;
        for (int i = 0; i < size; i++)
            queue[i] = null;
        size = 0;
}

 

 

  • 迭代方法

        public Iterator<E> iterator()


        返回在此队列中的所有元素(既包括到期的,也包括未到期的)上进行迭代的迭代器。迭代器不以任何特定的顺序返回元素。允许并发修改。该方法不会抛出 ConcurrentModificationException,即该迭代器在迭代元素的过程中不会去检测队列是否有被其他线程修改。所谓修改即队列中元素的入队、出队或移除操作所带来的元素增减变化。


        弱一致迭代器。该迭代器只能反映出iterator方法在创建该迭代器时队列中元素的情况,而不能反映出创建该迭代器之后队列所发生的元素改变,即队列中元素的入队、出队或移除操作所带来的元素增减变化,该迭代器是一无所知的。

 

  • DelayQueue与Leader-Follwer模式

        代码分析是非常无聊的事情,代码的实现来自人类的一般思考或对事物的语言描述,这里通过一个通俗的业务场景来描述DelayQueue中的Leader-Follwer模式实现,其中各种场景问题思考都与代码实现是一一对应的,如对代码实现感兴趣就看看代码,不感兴趣知道怎么用就行了。

        

         业务背景是酒店门口的出租车等候乘客与乘客候车。出租车不允许插队或以无次序的方式拉客的,一般情况下是一辆车跟在另一辆车后面,等前面的车走了下一辆车再拉客(但也会有例外);候车的乘车也是在候车站台基于某种有序的方式排队等车。在候车和拉客的过程中将会面临如下场景和问题:


take方法需要面临的场景


          take方法好像酒店门口的出租车进入候车口载走乘客

 

        场景1:

 

出租车A来到宾馆门口的候车口
出租车司机A看到宾馆门口没有一个人
出租车司机A马上离开宾馆门口的候车口

 

 


        场景2:

出租车司机A来到宾馆门口的候车口,看到有乘客1在候车
出租车司机A问乘客1:你要走吗?
乘客1说:马上走
出租车司机A对乘客1说:上车吧。

 

        场景3:

出租车司机A来到宾馆门口的候车口,看到有乘客1在候车
出租车司机A问乘客1:你要走吗?
乘客1说:等我10分钟再走好吗

 

        场景3.1:

出租车司机A在宾馆门口竖了个牌子,表示这个候车口暂时归我独占了
出租车司机A在宾馆门口打了10分钟盹

 

        场景3.2:

出租车司机A在打盹10分钟之后睡醒了

 

        场景3.3......

出租车司机A看了一下宾馆门口的牌子,发现这个牌子是自己之前放的
出租车司机A把自己放的牌子收起来了,表示这个候车口不归我独占了

 

        场景3.4......

出租车司机A对乘客A说:10分钟到了,可以走了,然后把乘客1带走了。

 

        场景4:

出租车司机A来到宾馆门口的候车口,看到有乘客1在候车
出租车司机A问乘客1:你要走吗?
乘客1说:等我10分钟再走好吗

 

        场景4.1......

出租车司机A在宾馆门口竖了个牌子,表示这个候车口暂时归我独占了
出租车司机A在宾馆门口打了10分钟盹,并且上了一个10分钟的闹钟

 

        场景4.2......

出租车司机A打盹的2分钟后,宾馆门口又来了一个出租车司机B
出租车司机B来到宾馆门口的候车口,同样看到有乘客1在候车
出租车司机A问乘客1:你要走吗?
乘客1说:等我8分钟再走好吗

 

        场景4.3......

出租车司机B这时后发现宾馆门口竖了个牌子,说明在他之前有人已经拉了这个活
出租车司机B只好也停止宾馆门口候车区打盹,因为出租车司机A独占了这个候车口

 

        场景4.4......

出租车司机A打盹的10分钟内,陆陆续续来了其他的出租车司机
他们都发现了有乘客1在候车,但也都发现宾馆门口竖了个牌子
此刻出租车司机A还在打盹中,他不知道在他的车后面已经堵了好多其他出租车。
并且出租车司机A后面的司机没有上闹钟,他们现在都长睡不起

 

        场景4.5......

出租车司机A在打盹10分钟之后睡醒了,因为他的闹钟响了
出租车司机A看了一下宾馆门口的牌子,发现这个牌子是自己之前放的
出租车司机A把自己放的牌子收起来了,表示这个候车口不归我独占了

 

        场景4.6......

出租车司机A对乘客A说:10分钟到了,可以走了,然后把乘客1带走了。

 

        场景4.7......

出租车司机A在离开候车口时,不忘又检测了候车口属于自己的牌子收起来没有
出租车司机A又看了下候车口,看看还有没有其他乘客

 

        场景4.7.1......

出租车司机A发现候车口还有一个乘客(但也不清楚这个乘客是不是马上就要走)
出租车司机A对候车区大喊了一声,来活了
堵在候车口的其中一个司机(仅此一个)听到喊声,从睡梦中醒来
出租车司机A从候车区离开

 

        场景4.7.2......

唯一听到出租车司机A喊叫的那个司机X,重新检测候车口,发现有一个乘客2
然后出租车司机X重复场景4.1到场景4.7的过程,期间:
出租车司机X可能会运气很好,乘客2是一个马上就走的乘客,出租车司机X并不会叫醒他后面的出租车司机
出租车司机X也可能运气不好,乘客2也要等上一段时间才走,出租车司机X会像出租车司机A一样成为放一个自己牌子,他成了独占者

 

或者

        场景继续4.7.2......

 

 

出租车司机A发现候车口没有乘客
出租车司机A默默的离开
堵在候车口的其他司机依然在睡觉,不知道出租车司机A已经走了

 
      offer方法需要面临的场景


        offer方法就好像酒店门口的乘客来到候车口乘车离开

 


        场景1:

乘客N来到候车口排队
这个时候可能已经来了很多乘车的乘客,而现在站在这个队伍最前面的人是乘客M,
这个队伍是按照谁着急着走谁站在前面,最着急走的那个乘客会站在队伍的最前面

 

        场景2.1:

刚刚来的乘客N现在是最着急要走了那个人

他会把放在候车口处的牌子拿走(如果有的话)。候车口如果有牌子,说明有一个领头的出租车司机X独占了这个候车位,出租车司机X还在睡梦中等闹铃响,然后希望乘客M坐着他的车离开。
现在乘客N成了最着急要走的那个人,并且乘客N已经站在了队伍的最前面,比乘客M还要靠前。
现在候车口处的牌子已经被乘客N拿开了,出租车司机X已经没有独占候车口的权利了,但出租车司机X自己还不知道
乘客N喊了一声,有车要走吗,堵在候车口的其中一个司机(仅此一个,有可能是出租车司机X,或者不是)听到喊声,从睡梦中醒来,这个时候他将有机会拉乘客N,并重复场景4.1到场景4.7的过程而之前的乘客M会在之后坐车离开,如果又来了比乘客M着急的乘客L,则乘客M还得等乘客L先走

 

  • 问题思考

        思考1:如果此时乘客N不去移除候车口处的牌子(如果有的话),只是喊了一声有车要走吗,会怎么样?

        堵在候车口的其中一个司机(仅此一个,有可能是出租车司机X,或者不是)听到喊声,从睡梦中醒来,这个时候他将有机会拉乘客N。
        如果不是出租车司机X,他醒来以后发现了乘客N,但是他也发现这个候车口依然被最前面的一个出租车司机独占(但他并不知道是那个司机独占了),换句话说,如乘客N不去移除候车口处的牌子,即便有司机想带他走,也会因为出租车司机X独占了候车口,而继续在堵在出租车司机X后面,重新睡觉。

        思考2:为什么乘客N不去唤醒所有(notifyAll)沉睡中的司机,而只是唤醒(notify)其中一个?

        当前乘客N是最有可能马上离开的乘客,而他只需要一位司机为他服务就够了,唤醒所有的司机,没有任何好处,反而造成多个司机因争抢客户而浪费时间。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值