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;
        }
    }

}

 

posted @ 2022-06-05 19:38  数学与IT  阅读(58)  评论(0编辑  收藏  举报