1. 使用DelayQueue实现延迟队列
DelayQueue是JDK中提供的延迟队列的实现方式,它继承了AbstractQueue抽象类,并实现了BlockingQueue接口。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 略
}
可以看到,延迟队列使用了ReentrantLock保证线程安全,其底层实际上是优先队列PriorityQueue,队列内的元素是继承了Delayed接口的对象。
编写一个类实现Delayed接口。其中使用泛型来存储对象,使用long来表示对象可获取的时间。
public class DelayObject<T> implements Delayed {
private final T obj;
private final long availableTime;
public DelayObject(T obj, long delayTime) {
this.obj = obj;
// 由当前时间 + 延期时间
this.availableTime = System.currentTimeMillis() + delayTime;
}
/**
* 获取对象
*/
public T getObj() {
return obj;
}
/**
* 获取delay
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(availableTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/**
* 排序
*/
@Override
public int compareTo(Delayed o) {
long l = getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
return l == 0 ? 0 : l > 0 ? 1 : -1;
}
}
测试延迟队列:
public static void main(String[] args) {
// 创建延迟队列
DelayQueue<DelayObject<User>> queue = new DelayQueue<>();
// 填充队列
fillQueue(queue);
// 线程池执行任务
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
while (true) {
try {
DelayObject<User> delayObject = queue.take();
System.out.printf("Take user from delayed queue %s:%s at %s%n", delayObject.getObj().getId(),
delayObject.getObj().getName(),
LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
private static void fillQueue(DelayQueue<DelayObject<User>> queue) {
List<User> users = Arrays.asList(new User("1", "Alice"),
new User("2", "Bob"),
new User("3", "Celina"));
// 初始延迟
int delay = 5000;
for (User user : users) {
// 放入延迟队列
System.out.printf("Begin to offer user to delayed queue %s:%s at %s%n", user.getId(),
user.getName(), LocalDateTime.now());
queue.offer(new DelayObject<>(user, delay));
delay += 5000;
}
}
2. 使用Redisson实现延迟队列
Redisson是Redis官方推荐的Java客户端,除了用来实现分布式锁之外,还可以用来实现延迟队列。
public void addDelay() {
// 根据队列名称获取阻塞队列
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE);
// 根据阻塞队列获取延迟队列
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
List<User> users = Arrays.asList(new User("1", "Alice"),
new User("2", "Bob"),
new User("3", "Celina"));
int delay = 5;
// 插入延迟队列
for (User user : users) {
log.info("Begin to offer user to delayed queue {}:{} at {}", user.getId(), user.getName(),
LocalDateTime.now());
delayedQueue.offer(user, delay, TimeUnit.SECONDS);
delay += 5;
}
}
可以使用同样的方法从延迟队列中获取对象。
@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {
private static final String DELAY_QUEUE = "delay_queue";
@Resource
private RedissonClient redissonClient;
@Override
public void run(ApplicationArguments args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
RBlockingDeque<User> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE);
// 必须调用getDelayedQueue方法,否则获取不到已过期数据
redissonClient.getDelayedQueue(blockingDeque);
// 循环获取
while (true) {
try {
User user = blockingDeque.take();
log.info("Take user from delayed queue {}:{} at {}", user.getId(), user.getName(),
LocalDateTime.now());
} catch (InterruptedException e) {
log.error("Failed to take from blocking deque caused by {}", e.getMessage());
}
}
});
}
}