JUC——延迟队列

所谓的延迟队列最大的特征是它可以自动通过队列进行脱离,例如:现在有一些对象被临时保存着,但是有可能该集合对象是一个公共对象,那么里面的某些数据如果不在使用的时候就希望其可以在指定的时间达到后自动的消失。

DelayQueue是延迟队列主要的使用类,所谓的延迟队列其实就是=BlockingQueue+PriorityQueue+Delayed

Delayed接口定义如下:

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

DelayQueue类定义如下:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E>

延迟队列的基本使用

下面编写一个简单的延迟队列,延迟队列的本质就是到点后自动离开,那么就一定要一定要设置一个离开的时间点

范例:使用DelayQueue进行延迟队列的定义,以朋友聚会离开为例

package so.strong.mall.concurrent;

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

class Member implements Delayed {
    private String name;  //聚会人员的名字
    private long expire;  //失效时间,人员离开的时间,单位毫秒
    private long delay;  //设置延迟时间,单位毫秒(只呆多久)

    /**
     * 设置参与队列之中的用户信息
     *
     * @param name  用户的额姓名
     * @param delay 延迟时间
     * @param unit  时间处理单位
     */
    public Member(String name, long delay, TimeUnit unit) {
        this.name = name;
        this.delay = TimeUnit.MILLISECONDS.convert(delay, unit); //保存延迟的时间
        this.expire = System.currentTimeMillis() + this.delay; //失效时间=当前时间加上延迟时间
    }

    @Override
    public long getDelay(TimeUnit unit) { //计算延迟是否到达
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) { //决定优先级队列的弹出操作
        return (int) (this.delay - this.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() {
        return this.name + "预计" + TimeUnit.SECONDS.convert(this.delay, TimeUnit.MILLISECONDS) + "秒后离开,现在已经到点了";
    }
}

public class DelayDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("*************准备聚会*************");
        DelayQueue<Member> delayQueue = new DelayQueue<>();//设置延迟队列
        delayQueue.add(new Member("Jack", 2, TimeUnit.SECONDS));
        delayQueue.add(new Member("Rose", 4, TimeUnit.SECONDS));
        while (!delayQueue.isEmpty()) { //如果聚会还有人
            Delayed delayed = delayQueue.poll(); //从队列里面取出数据
            System.out.println("[poll={" + delayed + "}]" + System.currentTimeMillis());
            TimeUnit.MILLISECONDS.sleep(500); //延迟500毫秒
        }
    }
}
*************准备聚会*************
[poll={null}]1528094341989
[poll={null}]1528094342489
[poll={null}]1528094342990
[poll={null}]1528094343490
[poll={Jack预计2秒后离开,现在已经到点了}]1528094343991
[poll={null}]1528094344491
[poll={null}]1528094344991
[poll={null}]1528094345491
[poll={Rose预计4秒后离开,现在已经到点了}]1528094345992

使用延迟队列的最大特征就是想吃自助餐一样,给一个固定时间,只要到达了卓哥限制或者说自己定义的离开的时间,那么就可以离开。就可以使用poll()从队列里面把人给拽出来,如果不到时间不能拽。

 

延迟队列案例

下面以学生考试为例做一个说明,现在学生考试里面就可以有许多的学生参与考试,而且老师需要进行学生考试的监督,每一个学生考试的时间是不同的、,但是不管有多不同,一旦到点后一定要结束考试。本次利用学生的类概念和老师监督考试的概念来实现延迟队列的定义操作。

范例:教师监督学生考试模型

package so.strong.mall.concurrent;
import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class Student implements Delayed {
    private String name;
    private long submitTime; //学生交卷时间,毫秒
    private long workTime; //实际考试时间,毫秒

    public Student(String name, long workTime, TimeUnit unit) {
        this.name = name;
        this.workTime = TimeUnit.MILLISECONDS.convert(workTime, unit);
        this.submitTime = System.currentTimeMillis() + this.workTime;
    }

    public void exam() { //考试处理
        System.out.println("[" + this.name + "交卷-{" + this.submitTime + "}],交卷时间: "
                + System.currentTimeMillis() + ",花费时间:" + this.workTime);
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.submitTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (this.workTime - this.getDelay(TimeUnit.MILLISECONDS));
    }
}

class Teacher implements Runnable {  //老师也是一个线程
    private int studentCount = 0; //参与考试的学生数量
    private int submitCount = 0; //保存交卷的学生个数
    private DelayQueue<Student> students = null;

    public Teacher(DelayQueue<Student> students, int studentCount) {
        this.students = students; //保存所有的学生信息
        this.studentCount = studentCount; //保存学生数量
    }

    @Override
    public void run() {
        System.out.println("************同学们开始答题*********");
        try {
            while (this.submitCount < this.studentCount) { //还有人未交卷
                Student student = this.students.poll();
                if (student != null) { //有人出队列了,表示有人交卷了
                    student.exam();//交卷处理
                    this.submitCount++; //交卷人数+1
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("************同学们结束考试*********");
    }
}


public class TestingDemo {
    public static void main(String[] args) throws Exception{
        Random random = new Random();// 利用随机数来模拟一个不同的交卷时间
        DelayQueue<Student> students = new DelayQueue<>();
        for (int i = 0; i < 10; i++) {
            int time = random.nextInt(10);
            while (time < 3) { //必须保证考试时间大于3秒
                time = random.nextInt(10);
            }
            students.add(new Student("学生-" + i, time, TimeUnit.SECONDS));
        }
        new Thread(new Teacher(students, 10)).start();
    }
}
************同学们开始答题*********
[学生-0交卷-{1528095504072}],交卷时间: 1528095504072,花费时间:8000
[学生-2交卷-{1528095501072}],交卷时间: 1528095504072,花费时间:5000
[学生-6交卷-{1528095500072}],交卷时间: 1528095504072,花费时间:4000
[学生-9交卷-{1528095499072}],交卷时间: 1528095504072,花费时间:3000
[学生-8交卷-{1528095505072}],交卷时间: 1528095505072,花费时间:9000
[学生-5交卷-{1528095499072}],交卷时间: 1528095505072,花费时间:3000
[学生-7交卷-{1528095501072}],交卷时间: 1528095505072,花费时间:5000
[学生-4交卷-{1528095504072}],交卷时间: 1528095505072,花费时间:8000
[学生-1交卷-{1528095500072}],交卷时间: 1528095505072,花费时间:4000
[学生-3交卷-{1528095500072}],交卷时间: 1528095505072,花费时间:4000
************同学们结束考试*********  

延迟队列到达时间点之后根据优先级进行队列的弹出处理。

 

延迟队列实现数据缓存

 在延迟队列中有一个最大的特征就是到点后进行清除处理,那么在实际开发中,可以利用此机制来实现一个缓存的处理操作。正规的开发之中,往往都会利用数据层通过数据库进行该数据的取得,并且将这些数据变为VO的形式。

守护线程的作用:清楚队列中的数据,同时清楚map里面的数据。

 

范例:实现一个缓存的基础模型(以新闻缓存为例)

package so.strong.mall.concurrent;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class News { //定义一个新闻类
    private String title;
    private String note;

    public News(String title, String note) {
        this.title = title;
        this.note = note;
    }

    @Override
    public String toString() {
        return "[新闻数据]title" + this.title + ",note=" + this.note;
    }
}

class DelayItem<T> implements Delayed {
    private T item; //设置要保存的数据
    private long expire;  // 设置缓存的失效时间
    private long delay;  //设置要保存的时间

    public DelayItem(T item, long delay, TimeUnit unit) {
        this.item = item;
        this.delay = TimeUnit.MILLISECONDS.convert(delay, unit);
        this.expire = System.currentTimeMillis() + this.delay;

    }

    public T getItem() {
        return item;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (this.delay - this.getDelay(TimeUnit.MILLISECONDS));
    }
}

//定义一个缓存的操作类,该类之中需要用户设置保存的可以Key类型与value类型
class Cache<K, V> {
    //如果要实现多个线程的并发访问操作,必须考虑使用ConcurrentHashMap子类
    private ConcurrentHashMap<K, V> cacheObjectMap = new ConcurrentHashMap<>();
    private DelayQueue<DelayItem<Pair>> delayQueue = new DelayQueue<>();

    private class Pair { //定义一个内部类,该类可以保存队列之中的K与V类型
        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public Cache() {//如果要想清空不需要的缓存数据,则需要守护线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) { //守护线程要一直执行,当已经超时之后可以取出数据
                    DelayItem<Pair> item = Cache.this.delayQueue.poll();//通过延迟队列获取数据
                    if (item != null) { //已经有数据超时了
                        Pair pair = item.getItem();
                        Cache.this.cacheObjectMap.remove(pair.key, pair.value);
                    }
                }
            }
        }, "缓存守护线程");
        thread.setDaemon(true); //设置守护线程
        thread.start(); //启动守护线程
    }

    /**
     * 表示将要保存的数据写入缓存中个,如果一个对象重复保存了,则应该重置它的超时时间
     * @param key   要写入的k的内容
     * @param value 要写入的对象
     * @param time  保存的时间
     * @param unit  保存的时间单位
     */
    public void put(K key, V value, long time, TimeUnit unit) {
        //put()方法如果发现原来的key存在,则会用新的value替换掉旧的内容,同时返回旧的内容
        V oldValue = this.cacheObjectMap.put(key, value);
        if (oldValue != null) { //原来已经存储过此数据
            this.delayQueue.remove(key);
        }
        this.delayQueue.put(new DelayItem<>(new Pair(key, value), time, unit));
    }

    public V get(K key) {
        return this.cacheObjectMap.get(key);
    }
}

public class CacheDemo {
    public static void main(String[] args) throws Exception {
        Cache<String, News> cache = new Cache<>();
        cache.put("aaa", new News("aaa", "xxx"), 3, TimeUnit.SECONDS);
        cache.put("BBB", new News("BBB", "xxx"), 3, TimeUnit.SECONDS);
        System.out.println(cache.get("aaa"));
        System.out.println(cache.get("BBB"));
        TimeUnit.SECONDS.sleep(5);
        System.out.println(cache.get("aaa"));
        System.out.println(cache.get("BBB"));
    }
}
[新闻数据]titleaaa,note=xxx
[新闻数据]titleBBB,note=xxx
null
null  

缓存在实际开发之中是一个重要的开发组件,虽然在实际的工作之中会接触大量的缓存组件,但是其基本的实现原理可参考如上内容。

 

posted @ 2018-06-04 15:32  iTermis  阅读(629)  评论(0编辑  收藏  举报