涉及的类
Delay
DelayQueue的泛型,队列中的对象必须实现Delay接口
简介
1.延迟后执行操作的泛型接口
延迟后执行操作:不会立即执行,而是会等待延迟时间后才会执行
2.实现了Comparable接口,用于排序,排序的规则为延迟时间
3.getDelay()方法用于获取延迟执行的时间或者标记位,简单来说就是方法返回0或者小于0的数,则表示可以执行
DelayQueue
简介
1.长度没有限制
2.元素只有在延迟过期时才会被占用
将队列中加入一个元素并不会占用队列的位置,也就不能进行队列相关操作;但是它们会被视为正常元素,例如,size方法返回已过期和未过期元素的计数,通过peek可以访问元素
!!简单来说,只有到延迟的时间之后,才会被放到队列中,才能进行所有的队列相关操作。否则只能查看,不能移除。
3.队列的头是延迟在过去过期时间最长的延迟元素。如果没有延迟到期,则没有头,轮询将返回null。
此队列为有序队列,延迟剩余时间越少的排在最前面
4.当元素的getDelay(TimeUnit.NASECONDS)方法返回小于或等于零的值时,延迟到期,可以正式操作元素。
队列相关操作需要查阅相关资料
示例
思路:
1.模拟用户登录系统,当用户停留在系统超过4秒(判断用户线程是否存活或者检查用户Session或Token)。
2.对当前用户进行延迟统计。
VerifyDelay
模拟用户线程
1.用户来的时候创建,并设置过期时间(毫秒)
2.创建后,由于该对象实现了Runnable接口,可以作为线程执行
3.线程执行后,对象中保存当前线程对象
4.通过当前线程对象判断用户是否还活跃,返回用户的名称
package com.zhf.model.thread.juc.collections.queue.test;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* @Author: Haiven
* @Time: 2024/4/16 14:49
* @Description: TODO
*/
@Data
public class VerifyDelay implements Delayed,Runnable {
/** 当前线程对象 */
private Thread currThread;
/** 用户名 */
private String username;
/** 过期时间 */
private long expires;
public VerifyDelay( String username, long expires) {
this.username = username;
this.expires = expires + System.currentTimeMillis();
}
@Override
public void run() {
//模拟用户线程执行操作
// 保存当前线程对象,用于判断用户状态
currThread = Thread.currentThread();
//使用随机数,模拟用户在系统的停留时间
int i = new Random().nextInt(10);
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//用户执行信息(执行时间和用户名)
System.out.println("模拟用户线程执行相关操作:用户名 = { "+ username +" }, 执行时间 = {" + i + "}");
}
public String statUsername(){
//判断用户线程是否存活,存活则返回用户名, 不存活则返回null
if(currThread.isAlive()){
return username;
}else {
return null;
}
}
@Override
public long getDelay(@NotNull TimeUnit unit) {
//过期时间 - 当前系统时间 大于0表示没有过期, 小于/等于0表示过期
return unit.convert(expires - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NotNull Delayed o) {
//按照过期时间排序
long compare = getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
if(compare == 0){
return 0;
}
if(compare > 0){
return 1;
}else {
return -1;
}
}
}
MainTest
queue.take():为队列的阻塞操作,如果队列为空则阻塞知道队列中有元素为止
package com.zhf.model.thread.juc.collections.queue.test;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @Author: Haiven
* @Time: 2024/4/16 15:12
* @Description: TODO
*/
public class MainTest {
/**
* 设置过期时间为 4s, 当用户停留超过4s则统计
*/
private static final long EXPIRE = 4000L;
/**
* 模拟的用户线程数
*/
private static final int THREAD_COUNT = 8;
public static List<String> statUserName() throws InterruptedException {
ArrayList<String> userList = new ArrayList<>();
BlockingQueue<VerifyDelay> queue = new DelayQueue<>();
// 模拟用户访问
for (int i = 0; i < THREAD_COUNT; i++) {
VerifyDelay userThread = new VerifyDelay("用户 - " + i, EXPIRE);
queue.offer(userThread);
// 创建线程执行用户操作
Thread thread = new Thread(userThread);
thread.start();
}
for (int i = 0; i < THREAD_COUNT; i++) {
// 获取队列的头对象并返回,如果队列为空则阻塞
VerifyDelay delay = queue.take();
// 获取用户名,线程不存活则返回为空
String username = delay.statUsername();
if(StringUtils.isNotBlank(username)){
userList.add(username);
}
}
return userList;
}
public static void main(String[] args) throws InterruptedException {
List<String> result = statUserName();
//在这里main线程睡10s防止main线程跑的太快,用户线程还没跑完, 不睡也可以,就是打印的比较乱
TimeUnit.SECONDS.sleep(10);
System.out.println("==============超过4s的用户===============");
result.forEach(System.out::println);
}
}
执行结果
模拟用户线程执行相关操作:用户名 = { 用户 - 1 }, 执行时间 = {0}
模拟用户线程执行相关操作:用户名 = { 用户 - 5 }, 执行时间 = {1}
模拟用户线程执行相关操作:用户名 = { 用户 - 3 }, 执行时间 = {5}
模拟用户线程执行相关操作:用户名 = { 用户 - 4 }, 执行时间 = {7}
模拟用户线程执行相关操作:用户名 = { 用户 - 7 }, 执行时间 = {7}
模拟用户线程执行相关操作:用户名 = { 用户 - 0 }, 执行时间 = {8}
模拟用户线程执行相关操作:用户名 = { 用户 - 6 }, 执行时间 = {9}
模拟用户线程执行相关操作:用户名 = { 用户 - 2 }, 执行时间 = {9}
==============超过4s的用户===============
用户 - 0
用户 - 2
用户 - 4
用户 - 3
用户 - 6
用户 - 7
补充说明
由于系统执行代码片段需要时间(虽然很短),当用户执行时间刚好为4s的时候会有一定的毫秒差,导致统计失败