import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;
public class CacheUpdateUtil {
private final Node startNode = new Node(null);
private final Node finishNode = new Node(null);
private final AtomicReference<Node> tail = new AtomicReference<>(finishNode);
/**
* 作用:保证只有一个线程刷新缓存,其他线程等待,刷新完毕之后唤醒所有线程去获取缓存。
* 相比直接采用synchronized好处,当并发很大的时候,缓存刷新比较慢时,那么大量线程就会阻塞在锁中,
* 等到缓存刷新完成后并不能让等待的线程直接全部唤醒去获取,而是只能一个个地去获取锁去缓存查。影响效率。
* 存在问题:1、极低概率会重复刷新
* @param cacheFlush 这里刷新缓存最好是直接覆盖。
*/
public void updateOrWait(Supplier cacheFlush) {
// 保证只有一个线程进行缓存刷新
if(tail.compareAndSet(finishNode, startNode)) {
try {
//进行缓存刷新, 需要避免报错无法唤醒队列
cacheFlush.get();
} finally {
//缓存更新完毕,将队列的尾节点设置为完毕节点。
tail.getAndUpdate(a -> finishNode);
//唤醒所有等待的线程
Node h = startNode;
while ((h = h.next.get()) != finishNode && h != null) {
Thread t = h.t;
if (t != null && t.getState() == Thread.State.WAITING) {
LockSupport.unpark(t);
}
}
//只要这一步没有设为null,下一轮的肯定是不可用的。
//将startNode设为可用状态,让下一轮线程可以等待。
startNode.next.set(null);
}
} else {
//创建等待节点
Node n = new Node(Thread.currentThread());
do {
//获取尾节点
Node tailNode = tail.get();
//已经完成缓存更新,并且保证当前拿到的不是finishNode
if(tailNode == finishNode) {
return;
}
AtomicReference<Node> next = tailNode.next;
//将自己加入队列,旧值为null表示一定是最后一个节点
if(next.compareAndSet(null, n)) {
//把自己作为尾节点,保证finishNode必须是最后一个节点
Node preNode = tail.getAndUpdate(a -> a == finishNode ? finishNode : n);
if(preNode != finishNode) {
LockSupport.park(CacheUpdateUtil.class);
}
return;
}
} while (true);
}
}
private static class Node {
final Thread t;
final AtomicReference<Node> next = new AtomicReference<>();
public Node(Thread t) {
this.t = t;
}
}
}