队列(二)——优先队列PriorityQueue

 

队列通常采用FIFO(先进先出)策略,可以满足多数情况下的开发需求,但是也有其不足之处。
例如:

  • 我要小明帮我买早餐,
  • 第一次,“小明,你后天帮我买牛奶吧!”,
  • 过了一会儿,“小明,你明天帮我买豆浆呗!”。

这个案例中,我先叫小明买的牛奶,再叫他买的豆浆,如果按照普通队列,按顺序执行,先买牛奶显然是错的。

优先队列

优先队列满足了上述的开发需求,队列中的元素按照自然顺序(Comparator)进行排序,通过Comparable接口可以定制任意的优先策略。

常见的优先队列有:

  1. PriorityQueue<E> 优先队列
  2. PriorityBlockingQueue<E> 阻塞优先队列(线程安全的队列)
  3. DelayQueue<E extends Delayed> 延迟队列(函数使用了可重入锁,是线程安全的队列),延迟队列所有元素必须继承Delayed接口,Delayed接口又继承自Comparable接口,专门用于时间的比较。

这三者优先队列之间不存在继承关系,DelayQueue内部包含PriorityQueue实例,是一个优先队列的装饰者;PriorityBlockingQueue源码也不同与另外两个。

使用Demo

复用上一个文章的工具类,通过PriorityBlockingQueue实现上述中买早餐的案例,顺便展示Delayed接口的使用方法。

package com.sea.common.util;

import java.util.concurrent.Delayed;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

class DelayItem implements Delayed {
    private long milli;
    private String msg;

    @Override
    public String toString() {
        return msg;
    }

    public DelayItem(long milli, String msg) {
        this.milli = milli;
        this.msg = msg;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        /**
         * TimeUnit提供了时间转换的算法,如果确定了延迟的精度,直接return milli即可
         */
        return unit.convert(milli, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return this.milli == o.getDelay(TimeUnit.NANOSECONDS) ? 0
                : (this.milli > o.getDelay(TimeUnit.NANOSECONDS) ? 1 : -1);
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Handler<DelayItem> handler = new Handler<DelayItem>() {

            @Override
            public void handleMessage(DelayItem msg) throws Exception {
                Thread.sleep(1000);
                System.out.println(msg);
            }
        };

        BlockingLopper<DelayItem> lopper = new BlockingLopper<DelayItem>();

        lopper.setDummy(new DelayItem(Long.MAX_VALUE, ""));
        lopper.setHandler(handler);
        lopper.setQueue(new PriorityBlockingQueue<>(2));
        lopper.loop();

        lopper.offer(new DelayItem(200, "小明,你后天帮我买牛奶吧!"));
        lopper.offer(new DelayItem(100, "小明,你明天帮我买豆浆呗!"));
        System.out.println("--------------------");
        lopper.stopLoop();
    }
}

内容补充

在API中还有几个比较抢眼的名词,因为不是十分常用,这里简单介绍。

  1. ConcurrentLinkedQueue同步线性队列,一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。针对线程安全问题,java.util.concurrent 包下实现了多个线程安全的Collection和Map,名字以Concurrent开头,util包下有对应的非线程安全实现类,ConcurrentLinkedQueue与LinkedList功能相对应。
  2. Deque<E> 一个线性的 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。Deque看似更加灵活,然而实际开发远不及普通队列有用。

 

posted on 2018-02-02 10:40  疯狂的妞妞  阅读(168)  评论(0编辑  收藏  举报

导航