第07篇:Mybatis緩存裝飾器

MyBatis 對緩存的設計是非常巧妙的软免》【花樣很多,但卻不是真的花樣植兰。因為Mybatis只是對 Map數(shù)據(jù)結(jié)構(gòu)的封裝, 但是卻實現(xiàn)了很多挺好用的能力份帐。
如果單單從設計模式上的角度來,其實就是典型的裝飾器模式, 裝飾器模式其實并不難,所以我們不講設計模式, 本篇文章我們來看看Mybatils 緩存設計巧妙的點。

官方文檔

下面通過簡單的代碼review來分析下這11個緩存類設計的巧妙點楣导。(因為是對博客重構(gòu),歷史圖片就沒有補充,圖上只有10個)


一废境、模式分析


從目錄就很清晰看出,核心就是impl 包下面只有一個,其他都是裝飾器模式,在
decorators 包下

:::tip

其實上面就是Mybatis 關(guān)于 Cache 的核心實現(xiàn),其實看到這里還沒有很多知識點. 那么我們從中能學到什么呢? 如果真要找一條學習的點,那么就是:

設計要面向接口設計筒繁,而不是具體實現(xiàn)噩凹。 這樣當我們要重寫 Cache ,比如說我們不想底層用 HashMap 來實現(xiàn)了,其實我們只要實現(xiàn)一下 Cache 接口膝晾,然后替換掉PerpetualCache就可以了栓始。對于使用者其實并不感知。

:::

1.1 Cache

接口設計沒有什么好講的血当,提供獲取和添加方法幻赚,跟Map接口一樣。 本篇我們要一起Review的類都會實現(xiàn)該接口的臊旭。

(這句話簡直就是廢話,大佬勿噴,就是簡單提醒落恼。意思就是其實代碼不難)

public interface Cache {

  String getId();
  
  void putObject(Object key, Object value);
  
  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();
  
  ReadWriteLock getReadWriteLock();

}

1.2 PerpetualCache

這個類就是 Mybatis 緩存最底層的設計, 看一下就知道其實是對 Map 的封裝。
其實我們只要知道他是簡單的 HashMap 的封裝就可以了离熏。因為代碼實戰(zhàn)是太簡單了,沒啥分析的佳谦。

public class PerpetualCache implements Cache {
  // 唯一標識
  private final String id;
  // 就是一個HashMap結(jié)構(gòu)
  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }
  // 基本沒啥用,外層誰要用,誰重寫
  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

二、開始重頭戲

從這里我們主要一起看下,代碼設計的巧妙之處,一個一個研究下,以下這10個類滋戳∽昝铮看 Mybatis 是如何巧妙設計的。

2.1 BlockingCache

BlockingCache是一個簡單和低效的Cache的裝飾器,我們主要看幾個重要方法奸鸯。

public class BlockingCache implements Cache {

  private long timeout;
  //實現(xiàn)Cache接口的緩存對象
  private final Cache delegate;
  //對每個key生成一個鎖對象
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      //釋放鎖咪笑。 為什么不加鎖? 所以get和put是組合使用的,當get加鎖,如果沒有就查詢數(shù)據(jù)庫然后put釋放鎖娄涩,然后其他線程就可以直接用緩存數(shù)據(jù)了窗怒。
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    //1. 當要獲取一個key,首先對key進行加鎖操作,如果沒有鎖就加一個鎖,有鎖就直接鎖
    acquireLock(key);
    Object value = delegate.getObject(key);
    if (value != null) {
      //2. 如果緩存命中,就直接解鎖
      releaseLock(key);
    }
    //3. 當value=null, 就是說沒有命中緩存,那么這個key就會被鎖住,其他線程進來都要等待
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // 移除key的時候,順便清楚緩存key的鎖對象
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
  
  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();
    ReentrantLock previous = locks.putIfAbsent(key, lock);
    //如果key對應的鎖存在就返回,沒有就創(chuàng)建一個新的
    return previous == null ? lock : previous;
  }
  
  private void acquireLock(Object key) {
    Lock lock = getLockForKey(key);
    //1. 如果設置超時時間,就可以等待timeout時間(如果超時了報錯)
    if (timeout > 0) {
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());  
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {
      //2. 如果沒有設置,直接就加鎖(如果這個鎖已經(jīng)被人用了,那么就一直阻塞這里。等待上一個釋放鎖)
      lock.lock();
    }
  }
  
  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }  
}

建議看代碼注釋

方法 解釋
acquireLock 加鎖操作
getObject 進來加鎖,如果緩存存在就釋放鎖,不存在就不釋放鎖。
putObject 添加元素并釋放鎖
removeObject 移除key的時候,順便清楚緩存key的鎖對象
getLockForKey 如果key對應的鎖存在就返回,沒有就創(chuàng)建一個新的

思考

  1. 這個因為每次key請求都會加lock真的會很慢嗎? 我們舉兩種場景扬虚。

注意這個加lock并不是對get方法加lock,而是對每個要get的key來加lock努隙。

場景一: 試想一種場景,當有10個線程同時從數(shù)據(jù)庫查詢一個key為123的數(shù)據(jù)時候,當?shù)谝粋€線程來首先從cache中讀取時候辜昵,這個時候其他九個線程是會阻塞的荸镊,因為這個key已經(jīng)被加lock了。當?shù)谝粋€線程get這個key完成時候堪置,其他線程才能繼續(xù)走贷洲。這種場景來說是不好的,

場景二: 但是當?shù)谝粋€線程來發(fā)現(xiàn)cache里面沒有數(shù)據(jù)這個時候其他線程會阻塞晋柱,而第一個線程會從db中查詢,然后在put到cache里面诵叁。這樣其他9個線程就不需要在去查詢db了,就減少了9次db查詢雁竞。

2.2 FifoCache

FIFO( First Input First Output),簡單說就是指先進先出

如何實現(xiàn)先進先出呢? 其實非常簡單,當put時候,先判斷是否需要執(zhí)行淘汰策略,如果要執(zhí)行淘汰,就 移除先進來的。 直接通過 Deque API 來實現(xiàn)先進先出拧额。

  private final Cache delegate;
  private final Deque<Object> keyList;
  private int size;

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<Object>();
    this.size = 1024;
  }

@Override
  public void putObject(Object key, Object value) {
      //1. put時候就判斷是否需要淘汰
    cycleKeyList(key);
    delegate.putObject(key, value);
  }
  private void cycleKeyList(Object key) {
    keyList.addLast(key);
    //1. size默認如果大于1024就開始淘汰
    if (keyList.size() > size) {
      //2. 利用Deque隊列移除第一個碑诉。
      Object oldestKey = keyList.removeFirst();
      delegate.removeObject(oldestKey);
    }
  }

2.3 LoggingCache

從名字上看就是跟日志有關(guān), LoggingCache 會在 debug級別下把緩存命中率給統(tǒng)計出來,然后通過日志系統(tǒng)打印出來侥锦。

public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    //1. 打印緩存命中率
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

除此之外沒有什么其他功能进栽。我們主要看下他是如何統(tǒng)計緩存命中率的。其實很簡單恭垦。

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;
  //1. 總請求次數(shù)
  protected int requests = 0;
  //2. 命中次數(shù)
  protected int hits = 0;
 
  ...
}  

在get請求時候無論是否命中,都自增總請求次數(shù)( request ), 當get命中時候自增命中次數(shù)( hits )

public Object getObject(Object key) {
    //1. 無論是否命中,都自增總請求次數(shù)( `request` )
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      //2. get命中時候自增命中次數(shù)( `hits` )
      hits++;
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

然后我們看命中率怎么算 getHitRatio()

命中率 = 命中次數(shù) / 總請求次數(shù)

 private double getHitRatio() {
    return (double) hits / (double) requests;
  }

2.4 LruCache

LRU是Least Recently Used的縮寫快毛,即最近最少使用。

首先我們看如何實現(xiàn) LRU 策略番挺。
它其實就是利用 LinkedHashMap來實現(xiàn) LRU 策略, JDK 提供的 LinkedHashMap天然就支持 LRU 策略唠帝。
LinkedHashMap 有一個特點如果開啟LRU策略后,每次獲取到數(shù)據(jù)后,都會把數(shù)據(jù)放到最后一個節(jié)點,這樣第一個節(jié)點肯定是最近最少用的元素玄柏。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        //1. 判斷是否開始LRU策略
        if (accessOrder)
            //2. 開啟就往后面放
            afterNodeAccess(e);
        return e.value;
    }
在這里插入圖片描述

構(gòu)造中先聲明LRU淘汰策略,當size()大于構(gòu)造中聲明的1024就可以在每次
putObject時候?qū)⒁蕴囊瞥艚笏ァ_@點非常的巧妙,不知道你學習到了沒 ?

在這里插入圖片描述

2.5 ScheduledCache

定時刪除,設計巧妙,可以借鑒。

public class ScheduledCache implements Cache {

  private final Cache delegate;
  protected long clearInterval;
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    //1. 指定多久清理一次緩存
    this.clearInterval = 60 * 60 * 1000; // 1 hour
    //2. 設置初始值
    this.lastClear = System.currentTimeMillis();
  }

  public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    clearWhenStale();
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    clearWhenStale();
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    return clearWhenStale() ? null : delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    clearWhenStale();
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    //1. 記錄最近刪除一次時間戳
    lastClear = System.currentTimeMillis();
    //2. 清理掉緩存信息
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private boolean clearWhenStale() {
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

}

核心代碼

  1. 構(gòu)造中指定多久清理一次緩存(1小時)
  2. 設置初始值
  3. clearWhenStale() 核心方法
  4. 然后在每個方法中調(diào)用一次這段代碼,判斷是否需要清理粪摘。
private boolean clearWhenStale() {
    //1. 當前時間 - 最后清理時間,如果大于定時刪除時間,說明要執(zhí)行清理了瀑晒。
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

2.6 SerializedCache

從名字上看就是支持序列化的緩存,那么我們就要問了,為啥要支持序列化?

為啥要支持序列化?

因為如果多個用戶同時共享一個數(shù)據(jù)對象時徘意,同時都引用這一個數(shù)據(jù)對象苔悦。如果有用戶修改了這個數(shù)據(jù)對象,那么其他用戶拿到的就是已經(jīng)修改過的對象映砖,這樣就是出現(xiàn)了線程不安全间坐。

如何解決這種問題

  1. 加鎖當一個線程在操作時候,其他線程不允許操作
  2. 新生成一個對象,這樣多個線程獲取到的數(shù)據(jù)就不是一個對象了。

只看一下核心代碼

  1. putObject 將對象序列化成byte[]
  2. getObjectbyte[]反序列化成對象
public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      //1. 將對象序列化成byte[]
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }
private byte[] serialize(Serializable value) {
    try {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(bos);
      oos.writeObject(value);
      oos.flush();
      oos.close();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

 public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    //1. 獲取時候?qū)yte[]反序列化成對象
    return object == null ? null : deserialize((byte[]) object);
  }
  private Serializable deserialize(byte[] value) {
    Serializable result;
    try {
      ByteArrayInputStream bis = new ByteArrayInputStream(value);
      ObjectInputStream ois = new CustomObjectInputStream(bis);
      result = (Serializable) ois.readObject();
      ois.close();
    } catch (Exception e) {
      throw new CacheException("Error deserializing object.  Cause: " + e, e);
    }
    return result;
  }

這種就類似于深拷貝,因為簡單的淺拷貝會出現(xiàn)線程安全問題,而這種辦法,因為字節(jié)在被反序列化時,會在創(chuàng)建一個新的對象竹宋,這個新的對象的數(shù)據(jù)和原來對象的數(shù)據(jù)一模一樣劳澄。所以說跟深拷貝一樣。

Java開發(fā)之深淺拷貝

2.7 SoftCache

從名字上看,Soft其實就是軟引用蜈七。軟引用就是如果內(nèi)存夠,GC就不會清理內(nèi)存,只有當內(nèi)存不夠用了會出現(xiàn)OOM時候,才開始執(zhí)行GC清理秒拔。

如果要看明白這個源碼首先要先了解一點垃圾回收,垃圾回收的前提是還有沒有別的地方在引用這個對象了。如果沒有別的地方在引用就可以回收了飒硅。
本類中為了阻止被回收所以聲明了一個變量hardLinksToAvoidGarbageCollection砂缩,
也指定了一個將要被回收的垃圾隊列queueOfGarbageCollectedEntries

這個類的主要內(nèi)容是當緩存value已經(jīng)被垃圾回收了三娩,就自動把key也清理庵芭。

Mybatis 在實際中并沒有使用這個類。

public class SoftCache implements Cache {
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;
  private int numberOfHardLinks;

  public SoftCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
  }
}  

先看下變量聲明

hard Links To Avoid Garbage Collection
硬連接,避免垃圾收集
queue Of Garbage Collected Entries
垃圾要收集的隊列
number Of Hard Links
硬連接數(shù)量

@Override
  public void putObject(Object key, Object value) {
    //1. 清除已經(jīng)被垃圾回收的key
    removeGarbageCollectedItems();
    //2. 注意看SoftEntry(),聲明一個SoftEnty對象,指定垃圾回收后要進入的隊列
    //3. 當SoftEntry中數(shù)據(jù)要被清理,會添加到類中聲明的垃圾要收集的隊列中
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

  @Override
  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    if (softReference != null) {
      result = softReference.get();
      if (result == null) {
        //1. 如果數(shù)據(jù)已經(jīng)沒有了,就清理這個key
        delegate.removeObject(key);
      } else {
        // See #586 (and #335) modifications need more than a read lock 
        synchronized (hardLinksToAvoidGarbageCollection) {
          //2. 如果key存在,讀取時候加一個鎖操作,并將緩存值添加到硬連接集合中,避免垃圾回收
          hardLinksToAvoidGarbageCollection.addFirst(result);
          //3. 構(gòu)造中指定硬鏈接最大256,所以如果已經(jīng)有256個key的時候回開始刪除最先添加的key
          if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
            hardLinksToAvoidGarbageCollection.removeLast();
          }
        }
      }
    }
    return result;
  }

  @Override
  public void clear() {
    //執(zhí)行三清
    synchronized (hardLinksToAvoidGarbageCollection) {
      //1.清除硬鏈接隊列
      hardLinksToAvoidGarbageCollection.clear();
    }
    //2. 清除垃圾隊列
    removeGarbageCollectedItems();
    //3. 清除緩存
    delegate.clear();
  }

  private void removeGarbageCollectedItems() {
    SoftEntry sv;
    //清除value已經(jīng)gc準備回收了,就就將key也清理掉
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

2.8 SynchronizedCache

從名字看就是同步的緩存,從代碼看即所有的方法都被synchronized修飾雀监。

在這里插入圖片描述

2.9 TransactionalCache

從名字上看就應該能隱隱感覺到跟事務有關(guān),但是這個事務呢又不是數(shù)據(jù)庫的那個事務双吆。只是類似而已是, 即通過 java 代碼來實現(xiàn)了一個暫存區(qū)域,如果事務成功就添加緩存,事務失敗就回滾掉或者說就把暫存區(qū)的信息刪除,不進入真正的緩存里面会前。 這個類是比較重要的一個類,因為所謂的二級緩存就是指這個類好乐。既然說了??緩存就順便提一下一級緩存。但是說一級緩存就設計到 Mybatis架構(gòu)里面一個 Executor 執(zhí)行器

在這里插入圖片描述

所有的查詢都先從一級緩存中查詢


在這里插入圖片描述
在這里插入圖片描述

看到這里不由己提一個面試題,面試官會問你知道Mybatis 的一級緩存嗎?
一般都會說Mybatis 的一級緩存就是 SqlSession 自帶的緩存,這么說也對就是太籠統(tǒng)了瓦宜,因為 SqlSession其實就是生成 Executor 而一級緩存就是里面query方法中的 localCache蔚万。這個時候我們就要看下了localCache 究竟是什么?
看一下構(gòu)造,突然豁然開朗。原來本篇文章講的基本就是一級緩存的實現(xiàn)呀临庇。

在這里插入圖片描述

說到這里感覺有點跑題了反璃,我們不是要看 TransactionalCache 的實現(xiàn)嗎?

clearOnCommit 為false就是這個事務已經(jīng)完成了,可以從緩存中讀取數(shù)據(jù)了。

clearOnCommittrue ,這個事務正在進行中呢? 來的查詢都給你返回 null , 等到 commit 提交時候在查詢就可以從緩存中取數(shù)據(jù)了假夺。

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);
    // 真正的緩存
  private final Cache delegate;
  // 是否清理已經(jīng)提交的實物
  private boolean clearOnCommit;
  // 可以理解為暫存區(qū)
  private final Map<Object, Object> entriesToAddOnCommit;
  // 緩存中沒有的key
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<Object, Object>();
    this.entriesMissedInCache = new HashSet<Object>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // 先從緩存中拿數(shù)據(jù)
    Object object = delegate.getObject(key);
    if (object == null) {
      // 如果沒有添加到set集合中
      entriesMissedInCache.add(key);
    }
    // 返回數(shù)據(jù)庫的數(shù)據(jù)版扩。
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    //1. 是否清除提交
    clearOnCommit = false;
    //2. 暫存區(qū)清理,代表這個事務從頭開始做了,之前的清理掉
    entriesToAddOnCommit.clear();
    //3. 同上
    entriesMissedInCache.clear();
  }
    
  /** 
   * 將暫存區(qū)的數(shù)據(jù)提交到緩存中
   **/
  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    //如果緩存中不包含這個key,就將key對應的value設置為默認值null
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  // 移除缺失的key,就是這個緩存中沒有的key都移除掉
  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

}

2.10 WeakCache

從名字上看跟 SoftCache 有點關(guān)系,Soft引用是當內(nèi)存不夠用時候才清理, 而Weak 弱引用則相反, 只要有GC就會回收侄泽。 所以他們的類型特性并不是自己實現(xiàn)的礁芦,而是依賴于 Reference<T> 類的特性,所以代碼就不看了基本和 SoftCache 實現(xiàn)一摸一樣悼尾。

感謝您的閱讀柿扣,本文由 西魏陶淵明 版權(quán)所有。如若轉(zhuǎn)載闺魏,請注明出處:西魏陶淵明(https://blog.springlearn.cn/)

本文由mdnice多平臺發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末未状,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子析桥,更是在濱河造成了極大的恐慌司草,老刑警劉巖艰垂,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埋虹,居然都是意外死亡猜憎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門搔课,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胰柑,“玉大人,你說我怎么就攤上這事爬泥〖硖郑” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵袍啡,是天一觀的道長踩官。 經(jīng)常有香客問我,道長境输,這世上最難降的妖魔是什么卖鲤? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮畴嘶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘集晚。我一直安慰自己窗悯,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布偷拔。 她就那樣靜靜地躺著蒋院,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莲绰。 梳的紋絲不亂的頭發(fā)上欺旧,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音蛤签,去河邊找鬼辞友。 笑死,一個胖子當著我的面吹牛震肮,可吹牛的內(nèi)容都是我干的称龙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼戳晌,長吁一口氣:“原來是場噩夢啊……” “哼鲫尊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沦偎,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤疫向,失蹤者是張志新(化名)和其女友劉穎咳蔚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔驼,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡谈火,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匙奴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堆巧。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泼菌,靈堂內(nèi)的尸體忽然破棺而出谍肤,到底是詐尸還是另有隱情,我是刑警寧澤哗伯,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布荒揣,位于F島的核電站,受9級特大地震影響焊刹,放射性物質(zhì)發(fā)生泄漏系任。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一虐块、第九天 我趴在偏房一處隱蔽的房頂上張望俩滥。 院中可真熱鬧,春花似錦贺奠、人聲如沸霜旧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挂据。三九已至,卻和暖如春儿普,著一層夾襖步出監(jiān)牢的瞬間崎逃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工眉孩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留个绍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓浪汪,卻偏偏與公主長得像障贸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吟宦,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容