java无锁解决缓存穿透问题
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; } } }