1. 程式人生 > 其它 >java無鎖解決快取穿透問題

java無鎖解決快取穿透問題

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class CacheUpdateUtil<K> {

    private final Node startNode = new Node();
    private final Node finishNode = new Node();
    
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(); n.t = 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 { volatile Thread t; final AtomicReference<Node> next = new AtomicReference<>(); } }