当前位置:网站首页>如何使用ConcurrentLinkedQueue做一个缓存队列
如何使用ConcurrentLinkedQueue做一个缓存队列
2022-07-04 20:34:00 【小曲同学呀】
在项目中,我们可能会用到队列,那么ConcurrentLinkedQueue,是必不可少的一个。对于多线程环境下,想要实现线程安全,要么自己加锁,要么利用线程安全队列-------ConcurrentLinkedQueue。
先看一下使用ConcurrentLinkedQueue是如何做一个缓存队列的。
package server.utils;
import java.util.concurrent.ConcurrentLinkedQueue;
public class QueueUtils {
//用户缓存队列
private static final ConcurrentLinkedQueue<String> queueCache = new ConcurrentLinkedQueue<>();
private int rank;
//入队
public void offer(String userId) {
queueCache.offer(userId);
}
//判断元素是否存在
public boolean isExist(String userId) {
if (queueCache.contains(userId)) {
return true;
}
return false;
}
//获取排名
public int getRank(String userId) {
if (isExist(userId)) {
for (String id : queueCache) {
if (id.equals(userId)) {
break;
}
rank++;
}
System.out.printf("rank排名为" + rank);
return rank;
} else {
//不包括则加入队尾
offer(userId);
rank = queueCache.size() + 1;
System.out.printf("不包含用户则加入队尾" + rank);
return rank;
}
}
public static void main(String[] args) {
for (int i = 1; i < 6; i++) {
new QueueUtils().getRank("1" + i);
}
new QueueUtils().getRank("15");
}
}

总结一下常用的三个方法:peak(),poll()方法,offer()方法
peak()方法:
这个方法的作用就是获取队列头部的元素,只获取不移除,注意这个方法和下面的poll方法的区别啊!
public E peek() {
//[1]goto标志
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//[2]
E item = p.item;
//[3]
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
//[4]
else if (p == q)
continue restartFromHead;
else//[5]
p = q;
}
}
}
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p)) {
h.lazySetNext(h);
}
poll()方法:
这个方法是获取头部的这个节点,如果队列为空则返回null;
public E poll() {
//这里其实就是一个goto的标记,用于跳出for循环
restartFromHead:
//[1]
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
//[2]如果当前节点中存的值不为空,则CAS设置为null
if (item != null && p.casItem(item, null)) {
//[3]CAS成功就更新头节点的位置
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//[4]当前队列为空,就返回null
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//[5]当前节点和下一个节点一样,说明节点自引用,则重新找头节点
else if (p == q)
continue restartFromHead;
//[6]
else
p = q;
}
}
}
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
offer()方法:
这个方法的作用就是在队列末端添加一个节点,如果传递的参数是null,就抛出空指针异常,否则由于该队列是无界队列,该方法会一直返回true,而且该方法使用CAS算法实现的,所以不会阻塞线程;
//队列末端添加一个节点
public boolean offer(E e) {
//如果e为空,那么抛出空指针异常
checkNotNull(e);
//将传进来的元素封装成一个节点,Node的构造器中调用UNSAFE.putObject(this, itemOffset, item)把e赋值给节点中的item
final Node<E> newNode = new Node<E>(e);
//[1]
//这里的for循环从最后的节点开始
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
//[2]如果q为null,说明p就是最后的节点了
if (q == null) {
//[3]CAS更新:如果p节点的下一个节点是null,就把写个节点更新为newNode
if (p.casNext(null, newNode)) {
//[4]CAS成功,但是这时p==t,所以不会进入到这里的if里面,直接返回true
//那么什么时候会走到这里面来呢?其实是要有另外一个线程也在调用offer方法的时候,会进入到这里面来
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q) //[5]
p = (t != (t = tail)) ? t : head;
else //[6]
p = (p != t && t != (t = tail)) ? t : q;
}
}
总结:
其实还有几个方法没说,但是感觉比较容易就不浪费篇幅了,有兴趣的可以看看:size方法用于计算队列中节点的数量,可是由于没有加锁,在并发的条件下不准确;remove方法删除某个节点,其实就是遍历然后用equals方法比较item是不是一样,只不过如果存在多个符合条件的节点只删除第一个,然后返回true,否则返回false;contains方法判断队列中是否包含指定item的节点,也就是遍历,很容易;
最麻烦的就是offer方法和poll方法,offer方法是在队列的最后面添加节点,而poll是获取头节点,并且删除第一个真正的队列节点(注意,节点分为两种,一种是哨兵节点,一种是真正的存了数据的节点啊),还简单的说了一下poll方法和peek方法的区别,后者只是获取,而不删除啊!
边栏推荐
- Procurement in software development
- NetWare r7000 Merlin system virtual memory creation failed, prompting that the USB disk reading and writing speed does not meet the requirements. Solution, is it necessary to create virtual memory??
- LambdaQueryWrapper用法
- 插入排序,选择排序,冒泡排序
- [1200. Différence absolue minimale]
- 杰理之AD 系列 MIDI 功能说明【篇】
- 【optimtool.unconstrain】无约束优化工具箱
- IIC (STM32)
- MYSQL 用!=查询不出等于null的数据,解决办法
- torch. Tensor and torch The difference between tensor
猜你喜欢

Huawei ENSP simulator configures DHCP for router

改善机器视觉系统的方法

Can be displayed in CAD but not displayed in print
![[observation] Lenovo: 3x (1+n) smart office solution, releasing the](/img/e4/f660461c42eb81ab8c94fd87503a83.png)
[observation] Lenovo: 3x (1+n) smart office solution, releasing the "multiplier effect" of office productivity

【C语言】符号的深度理解

Render function and virtual DOM

华为ensp模拟器 DNS服务器的配置

LambdaQueryWrapper用法

FastDfs的快速入门,三分钟带你上传下载文件到云服务器

Stealing others' vulnerability reports and selling them into sidelines, and the vulnerability reward platform gives rise to "insiders"
随机推荐
[ 每周译Go ] 《How to Code in Go》系列文章上线了!!
admas零件名重复
PS vertical English and digital text how to change direction (vertical display)
What are the functional modules of RFID warehouse management system solution
Redis:Redis配置文件相关配置、Redis的持久化
HWiNFO硬件检测工具v7.26绿色版
Redis cache
Use of redis publish subscription
Detailed explanation of multi-mode input event distribution mechanism
JS卡牌样式倒计时天数
华为ensp模拟器 三层交换机
redis RDB AOF
Daily question-leetcode556-next larger element iii-string-double pointer-next_ permutation
Jerry's ad series MIDI function description [chapter]
__init__() missing 2 required positional arguments 不易查明的继承错误
[solution] paddlepaddle 2 X call static graph mode
Golang中UTF编码和字符集
解析互联网时代的创客教育技术
杰理之AD 系列 MIDI 功能说明【篇】
华为模拟器ensp常用命令