阻塞队列基础——实现截至日期倒计时

150 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

编写一个计时器在编程中是比较常见的,特别是要设计一个考试的倒计时工具,以便提醒我们自己,考试的日期。 我们这里采用阻塞队列的方式来设计考试倒计时器,当然不代表这种方式是高效的,这里只作为一个示范。

在设计之前要清楚的几个概念:

  • 什么是阻塞队列?
  • 阻塞队列与普通队列有什么不同?

首先它是一个队列,那么第一点要符合队列这种数据结构的特点。

先进先出FIFO(first in first out),即先push进入队列的元素,可以先取出,而后进入队列的元素,只能后取出,就像我们去排队买东西一样,提前去排队的就可以提前买到你要买的东西。

8fc027fc2a0a364ce251b2800e2f12f3.png

阻塞队列与普通队列不同的地方就在“阻塞”了:

  • 阻塞队列为空时,取元素操作会被阻塞,直到阻塞队列有元素。

  • 阻塞队列容量已满时,入元素操作会被阻塞,直到阻塞队列有容量。

我们也可以从源码中看到阻塞队列的父类是Queue类

be90c269efa9fdfc3db4ecfbbbf08501.png

里面有这么些方法,这里就不详细了,可查阅手册使用方法:

1)放入数据

  • add(Object e)
  • offer(Object e)
  • put(Object e)

2)获取数据

  • pull()
  • take()
  • drainTo()

392ea8e3c015a7f7615afc0038fb3c10.png

在多线程环境中,由于消息队列使用的需要,我们引入一个设计模型,称为“生产者消费者模型”。

消息队列作为一个类似队列的一个数据结合就相当于我们的仓库,生产者将数据产出后放入我们的消息队列这样的“仓库”中,我们的消费者需要调取数据的时候就可以从我们的“仓库”中获得,而我们取到的数据按照生产者生产的数据顺序,先生产完成就可以先取得,阻塞队列与普通队列的区别就在于当队列为空的时候,我们的消费者获取元素的操作会被阻塞,直到队列中有元素了才会唤醒消费者线程去获得数据。

我们编写一个demo来模拟这样一个生产者与消费者两个线程的工作过程。

  • 定义生产者线程,实现线程接口,构造方法注入阻塞队列,将1~5五个数字存入阻塞队列中。
class Producer implements Runnable{
    private BlockingQueue<Integer> queue;
    Producer(BlockingQueue<Integer> queue){
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            for (int i = 1 ; i < 6 ; i++) {
                Thread.sleep(20);
                queue.put(i);
                System.out.println("生产的元素:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 按照同样的方法,定义消费者线程,这里模拟随机时刻消费,取得元素的过程。
class Consumer implements Runnable{
    private BlockingQueue<Integer> queue;
    Consumer(BlockingQueue<Integer> queue){
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            while (true){
                Thread.sleep(new Random().nextInt(1000));
                Integer i = queue.take();
                System.out.println("消费的元素:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 编写测试的方法,定义阻塞队列的容量为3,即至多可存放三个元素。
public class Test {
    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(3);
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

通过我们运行得到的结果,可以看出:

1、我们的阻塞队列是先进先出的。在队列中,同时有2,3,4三个元素的时候,最先取得的元素是2,因为2是最早被生产出来进入阻塞队列的。

2、队列的最大容量是3。在队列到达队满状态(即生产出2,3,4三个元素的情况下,就不会继续生产),这个时候阻塞队列会让生产线程进入等待状态,直到消费线程消费完一个元素(即元素2)后,生产线程才继续生产出新的元素(即元素5)。

b31238222d4ec79bf1ea1a2e8fa23680.png

通过上面简单的讲解,我们已经可以基本掌握阻塞队列的使用方法了。下面就开始进入倒计时的设计:

  • 首先要明确几个需求:
    • 生产者生产,消费者消费的是什么东西?
    • 如何计算倒计时剩余的时间?
    • 如何在控制台中刷新倒计时更新数据?

很简单,首先,生产线程根据获取系统的时间与我们设定的某个考试倒计时截至时间做一个比较,计算差值就可以得到我们的剩余时间。将生产得到的时间,存入阻塞队列中,消费线程就可以根据需要获取倒计时剩余时间,并且打印在控制台中。但由于每秒刷新的需要,控制台的数据过多,我们可以对控制台的数据进行回退处理。

具体实现代码如下:

package com.interview.time;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class MyRestTime {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(1);
        new Thread(new TimeProducer(blockingQueue)).start();
        new Thread(new TimeConsumer(blockingQueue)).start();
    }
}

class TimeProducer implements Runnable{
    private BlockingQueue blockingQueue;
    public TimeProducer(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }
    @Override
    public void run() {
        DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long unit_day = 1000*60*60*24;
        long unit_hour = 1000*60*60;
        long unit_minute = 1000*60;
        long unit_second = 1000;
        while (true){
            long currentTime = System.currentTimeMillis();

            try {
                Date endDate = simpleDateFormat.parse("2021-03-14 00:00:00");
                long restTime = endDate.getTime() - currentTime;
                String myRestTime = String.format("距离考试还有%d天%d时%d分%d秒", restTime / unit_day, restTime % unit_day / unit_hour
                        , restTime % unit_hour / unit_minute, restTime % unit_minute / unit_second);
                Thread.sleep(1000);
                blockingQueue.put(myRestTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

class TimeConsumer implements Runnable {
    private String restTime = "";
    private BlockingQueue<String> blockingQueue;
    TimeConsumer(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                if(restTime.length()>0){
                    for (char c : restTime.toCharArray()) {
                        System.out.print("\b");    // 输出退格符
                    }
                }
                restTime = blockingQueue.take();
                System.out.print(restTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

faaf7b9b0a5dc9d9a22bdc6eee22f75a.png

结果图示 | 图片来源CodeItEasy

我们这里采用ArrayBlockingQueue,即基于数组的阻塞队列

55d2c1e1d8c9d85a5544145b5c7a452c.png

还有基于链表的阻塞队列

7127727fa8c7a182d16238a8c5395658.png

延迟队列

20f56b3c70cb7a2791884053923bc6a1.png

基于优先级的阻塞队列

9415de63110640429149332a8a49450a.png

同步队列

46f6e152ca57140884b3582b1d2abe00.png

这些队列都是BlockingQueue的实现类,在实际应用中也有不同的用途。

原创:本文由CodeItEasy公众号(lsir_34567)原创,编辑:原虫子
CSDN创作者:刘先生的u写倒了 ( blog.csdn.net/weixin_4379… )
微信公众平台创作者:CodeItEasy
掘金平台创作者:刘先生的u写倒了