利用DelayQueue实现一个定时缓存

利用DelayQueue实现一个可定时的缓存,使用单例模式,以便调用的时候都是同一个对象,定时器用DelayQueue来实现,还可以支持多线程情况下的调用

DelayQueue简介

DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
因为DelayQueue是基于PriorityQueue实现的,PriorityQueue底层是一个堆,可以按时间排序,所以等待队列本身只需要维护根节点的一个定时器就可以了,而且插入和删除都是时间复杂度都是logn,资源消耗很少,作为一个缓存的定时装置是非常适合的
使用 DelayQueue 需要传入一个Delayed对象,要实现两个方法
1 getDelay(TimeUnit unit) 表示当前对象关联的延时,在本例中表示生存时间
2 compareTo(Delayed o) 堆在进行删除和插入时需要进行对比,所以要传入一个比较器

实例展示

public class ExpiryMap {

    private static final Logger log = LoggerFactory.getLogger(ExpiryMap.class);

    private ExpiryMap() {
    }
    private static volatile ExpiryMap expiryMap;
    /**
     * 键值对集合
     */
    private final static Map<String, Object> map = new HashMap<>();
    /**
     * 使用阻塞队列中的等待队列,因为DelayQueue是基于PriorityQueue实现的,而PriorityQueue底层是一个最小堆,可以按过期时间排序,
     * 所以等待队列本身只需要维护根节点的一个定时器就可以了,而且插入和删除都是时间复杂度都是logn,资源消耗很少
     */
    private final static DelayQueue<DelayData<String>> delay = new DelayQueue<>();

    //使用单例模式,加上双重验证,可适用于多线程高并发情况
    public static  ExpiryMap getInstance() {
        if (expiryMap == null) {
            synchronized (ExpiryMap.class) {
                if (expiryMap == null) {
                    expiryMap = new ExpiryMap();
                    //生成一个线程扫描等待队列的值
                    ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
                    scheduledExecutorService.execute(()->{
                        while (true) {
                            try {
                                //此方法是阻塞的,没有到过期时间就阻塞在这里,直到取到数据
                                DelayData<String> take = delay.take();
                                map.remove(take.data);
                            } catch (InterruptedException e) {
                                log.error("ExpiryMap线程被打断");
                            }
                        }
                    });
                }
            }
        }
        return expiryMap;
    }

    /**
     * 添加缓存
     *
     * @param key    键
     * @param data   值
     * @param expire 过期时间,单位:毫秒, 0表示无限长
     */
    public void put(String key, Object data, long expire) {
        map.put(key,data);
        //当等于0时,就不把过期时间放进队列里了,值在代码运行期间会一直存在
        if (expire != 0) {
            delay.offer(new DelayData<>(key, System.currentTimeMillis() + expire));
        }
    }

    /**
     * 读取缓存
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return map.get(key);
    }

    /**
     * 清除缓存
     *
     * @param key 键
     * @return 值
     */
    public Object remove(String key) {
        return map.remove(key);
    }

    /**
     * 查询当前缓存的键值对数量
     * @return 数量
     */
    public int size() {
        return map.size();
    }
    //试下Delayed接口
    static class DelayData<T> implements Delayed{
        private T data;
        //到期时间
        private long expire;

        public DelayData(T data, long expire) {
            this.data = data;
            this.expire = expire;
        }

        //如果返回小于0就代表过期了
        @Override
        public long getDelay(TimeUnit unit) {
            //expire是过期时的时间
            long diffTime= expire- System.currentTimeMillis();
            return unit.convert(diffTime,TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return (int)(this.expire - ((DelayData<T>) o).getExpire());
        }

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }

        public long getExpire() {
            return expire;
        }

        public void setExpire(long expire) {
            this.expire = expire;
        }
    }

写一个测试方法

分别像缓存中放入3个值,时间分别是1s,2s,3s,每过1s,打印下结果

    public static void main(String[] args) throws InterruptedException {
        ExpiryMap instance = getInstance();
        instance.put("key1","key1",1000);
        instance.put("key2","key2",2000);
        instance.put("key3","key3",3000);
        System.out.println(instance.get("key1"));
        System.out.println(instance.get("key2"));
        System.out.println(instance.get("key3"));
        Thread.sleep(1000);
        System.out.println("-----------------");
        System.out.println(instance.get("key1"));
        System.out.println(instance.get("key2"));
        System.out.println(instance.get("key3"));
        Thread.sleep(1000);
        System.out.println("-----------------");
        System.out.println(instance.get("key1"));
        System.out.println(instance.get("key2"));
        System.out.println(instance.get("key3"));
        Thread.sleep(1000);
        System.out.println("-----------------");
        System.out.println(instance.get("key1"));
        System.out.println(instance.get("key2"));
        System.out.println(instance.get("key3"));
    }

输出结果

可以看出,缓存每过一秒就会被删除一个元素,删除时间是根据传入的过期时间清除的

key1
key2
key3
-----------------
null
key2
key3
-----------------
null
null
key3
-----------------
null
null
null
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容