DelayQueue延时队列简单使用

        DelayQueue是JDK1.5引入到工具,位置在java.util.concurrent。从包路径我们就能知道该工具是给多线程使用到。我们先看看官方注释:

An unbounded blocking queue of Delayed elements, in which an element can only be taken when its delay has expired. The head of the queue is that Delayed element whose delay expired furthest in the past. If no delay has expired there is no head and poll will return null. Expiration occurs when an element's getDelay(TimeUnit.NANOSECONDS) method returns a value less than or equal to zero. Even though unexpired elements cannot be removed using take or poll, they are otherwise treated as normal elements. For example, the size method returns the count of both expired and unexpired elements. This queue does not permit null elements.

延迟元素的一种无限阻塞队列,其中一个元素只能在其延迟过期时被占用。队列的头部是延迟过期时间最长的延迟元素。如果没有延迟到期,则没有标头,轮询将返回null。当元素的getDelay(TimeUnit.NANOSECONDS)方法返回一个小于或等于零的值时,就会发生过期。即使未过期的元素不能使用take或poll删除,它们也被视为正常元素。例如,size方法返回过期和未过期元素的计数。此队列不允许空元素。

        DelayQueue继承了AbstractQueue,并且实现了BlockingQueue,所以DelayQueue之所以能实现延时到特性,就是从BlockingQueue身上实现到take()方法。

public E take() throws InterruptedException {
		// 加锁的一个动作 保证获取数据的安全性
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
            	//peek 方法是去头部数据即第一个数据
                E first = q.peek();
                if (first == null)
                	//说明队列为空 调用condition.await()方法,会使得当前线程释放lock然后加入到等待队列中
                    available.await();
                else {
                	//如果第一个数据不为空 获取消息体的延迟时间(getDelay() 会在消息体内重写 自定义添加延迟时间)
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                    	//如果延迟时间 小于等于0 说明已经达到了延迟时间 调用poll方法返回消息体
                        return q.poll();
                    //如果延迟时间不为空的话 说明还需要等待一段时间 此时重新循环 所以讲frist置为空
                    first = null; 
                    if (leader != null)
                    	//这里用到了Leader/Followers模式 有兴趣的话可以去百度一下这个模式
                    	//如果leader 不为空 说明已经有线程在监听 即有线程在优先获取队列的首元素
                    	//释放当前线程获取的锁 加入到等待队列中 即 当前线程变成了Followers
                        available.await();
                    else {
                    	//如果没有leader 说明没有线程在监听(没有线程在优先获取队列的首元素)
                    	// 将当前线程置为leader线程 
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	//让当前线程最长等待 delay 时间 等待
                            available.awaitNanos(delay);
                        } finally {
                        	//释放leader权限
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	// 如果leader 为空 且 队列中有数据 说明没有其他线程在在等待 
            if (leader == null && q.peek() != null)
            	//唤醒睡眠的线程
                available.signal();
            //释放锁
            lock.unlock();
        }
    }
}

        接下来我们看看添加元素,DelayQueue队列的元素必须实现Delayed接口,重写getDelay(TimeUnit unit)方法和compareTo(T o)就能实现延时效果。

        假设有这么一个场景:

        有一些随时可能会变动的数据存在数据库中,外部接口频繁的读取这些数据,给数据库带来很大的开销。所以我想了个办法,把数据缓存到Redis,但是我到Redis应用并不能感知到数据的变化,(因为业务不会同步到我这边)。为了让缓存能及时更新到最新数据,我给缓存设置了过期时间,业务每次从我的Redis中读取,如果过期我就再次查库缓存。这个方案本没问题,但是最近发现开发环境的Redis切换到内网了,每次连接Redis会很慢,于是我就想把缓存从Redis移到JVM。但是呢,只有Redis应用才能连接数据库。于是就有了现在的交互:在Redis应用中查询数据库并缓存,JVM应用从Redis应用读取缓存,并使用DelayQueue延时队列的过期特性来同步Redis的更新。

        接下来我们来试着实现一下:

@Slf4j
public static class CacheMap implements Delayed {

            /**
         * JVM层存放数据的容器
         */
        private static final Map<String, List<String>> CACHE = new HashMap<>();

        private static final DelayQueue<CacheMap> QUEUE = new DelayQueue<>();

        /**
         * 过期时间
         */
        private final long delayTime;

        /**
         * 构造函数 初始化过期时间
         * @param delayTime 过期时间
         */
        public CacheMap(long delayTime) {
            this.delayTime = delayTime + System.currentTimeMillis();
        }

        /**
         * 实现getDelay方法
         * 过期时间 - 当前时间 < 0 表示过期
         * @param unit the time unit
         * @return
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         * 排序方式
         * @param o the object to be compared.
         * @return
         */
        @Override
        public int compareTo(Delayed o) {
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
        }

        public static List<String> getList(String key) {
            if (CACHE.isEmpty()) {
                initCache();
            }
            log.info("从缓存取:{}", CACHE);
            return CACHE.get(key);
        }

        /**
         * 模拟更新缓存
         */
        public static void initCache() {

            log.info("初始化缓存");

            List<String> appList = new ArrayList<>();
            appList.add("2088");
            appList.add("wx8976");
            List<String> webList = new ArrayList<>();
            webList.add("manager");
            webList.add("api");
            CACHE.put("app", appList);
            CACHE.put("web", webList);

            // 存放到延时队列
            QUEUE.offer(new CacheMap(60000));
        }

        public static void clear() {
            log.info("清理过期缓存");
            CACHE.clear();
       }    
}
@Slf4j
public class MyDelQueue {



    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            int count = 0;
            while (true) {
                try {
                    Thread.sleep(5000);
                    String key;
                    if (count % 2 == 0) {
                        key = "app";
                    } else {
                        key = "web";
                    }
                    List<String> list = CacheMap.getList(key);
                    log.info("读取到数据:{}", list);
                    count++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "操作缓存线程").start();

        new Thread(() -> {
            while (true) {
                try {
                    CacheMap.QUEUE.take();
                    log.info("缓存过期了");
                    CacheMap.clear();

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        }, "延时删除线程").start();

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中的DelayQueue是一个特殊的队列,它只允许在指定的延迟时间之后才能从队列中取出元素。可以使用DelayQueue来实现一些延迟任务的功能,例如任务调度、缓存过期等。 DelayQueue基于PriorityQueue实现,但是它的元素必须实现Delayed接口,Delayed接口中定义了一个getDelay()方法,返回元素的延迟时间。 当从DelayQueue中取出元素时,如果该元素的延迟时间还没有到达,则该元素会被重新加入队列中,直到延迟时间到达。 以下是一个简单使用DelayQueue的例子: ```java import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueExample { public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedElement> delayQueue = new DelayQueue<DelayedElement>(); delayQueue.add(new DelayedElement("element1", 2000)); delayQueue.add(new DelayedElement("element2", 5000)); delayQueue.add(new DelayedElement("element3", 1000)); while (!delayQueue.isEmpty()) { DelayedElement element = delayQueue.take(); System.out.println("Taking element: " + element); } } static class DelayedElement implements Delayed { private String name; private long delayTime; public DelayedElement(String name, long delayTime) { this.name = name; this.delayTime = System.currentTimeMillis() + delayTime; } @Override public long getDelay(TimeUnit unit) { long diff = delayTime - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { if (this.delayTime < ((DelayedElement) o).delayTime) { return -1; } if (this.delayTime > ((DelayedElement) o).delayTime) { return 1; } return 0; } @Override public String toString() { return "DelayedElement{" + "name='" + name + '\'' + ", delayTime=" + delayTime + '}'; } } } ``` 在上面的例子中,我们创建了一个DelayQueue,并向其中添加了三个DelayedElement元素。每个元素都有一个延迟时间,分别为2秒、5秒和1秒。 在主线程中,我们不断地从DelayQueue中取出元素,直到队列为空。当元素的延迟时间还没有到达时,它会被重新加入队列中,直到延迟时间到达。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值