日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

apache缓存清理_深挖 Mybatis 源码:缓存模块

發布時間:2023/12/10 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 apache缓存清理_深挖 Mybatis 源码:缓存模块 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:AmyliaY

出自:Doocs開源社區

原文:my.oschina.net/doocs/blog/4549852


MyBatis 中的緩存分為一級緩存、二級緩存,但在本質上是相同的,它們使用的都是 Cache 接口的實現。在這篇文章里,我們就來分析 Cache 接口以及多個實現類的具體實現。

1、Cache 組件

MyBatis 中緩存模塊相關的代碼位于 org.apache.ibatis.cache 包 下,其中 Cache 接口 是緩存模塊中最核心的接口,它定義了所有緩存的基本行為。

public interface Cache {/*** 獲取當前緩存的 Id*/String getId();/*** 存入緩存的 key 和 value,key 一般為 CacheKey對象*/void putObject(Object key, Object value);/*** 根據 key 獲取緩存值*/Object getObject(Object key);/*** 刪除指定的緩存項*/Object removeObject(Object key);/*** 清空緩存*/void clear();/*** 獲取緩存的大小*/int getSize();/*** !!!!!!!!!!!!!!!!!!!!!!!!!!* 獲取讀寫鎖,可以看到,這個接口方法提供了默認的實現!!* 這是 Java8 的新特性!!只是平時開發時很少用到!!!* !!!!!!!!!!!!!!!!!!!!!!!!!!*/default ReadWriteLock getReadWriteLock() {return null;} }

如下圖所示,Cache 接口 的實現類有很多,但大部分都是裝飾器,只有 PerpetualCache 提供了 Cache 接口 的基本實現。

PerpetualCache

PerpetualCache(Perpetual:永恒的,持續的)在緩存模塊中扮演著被裝飾的角色,其實現比較簡單,底層使用 HashMap 記錄緩存項,也是通過該 HashMap 對象 的方法實現的 Cache 接口 中定義的相應方法。

public class PerpetualCache implements Cache {// Cache對象 的唯一標識private final String id;// 其所有的緩存功能實現,都是基于 JDK 的 HashMap 提供的方法private Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic int getSize() {return cache.size();}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}/*** 其重寫了 Object 中的 equals() 和 hashCode()方法,兩者都只關心 id字段*/@Overridepublic 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());}@Overridepublic int hashCode() {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}return getId().hashCode();} }

下面來看一下 cache.decorators 包 下提供的裝飾器,它們都直接實現了 Cache 接口,扮演著裝飾器的角色。這些裝飾器會在 PerpetualCache 的基礎上提供一些額外的功能,通過多個組合后滿足一個特定的需求。

BlockingCache

BlockingCache 是阻塞版本的緩存裝飾器,它會保證只有一個線程到數據庫中查找指定 key 對應的數據。

public class BlockingCache implements Cache {// 阻塞超時時長private long timeout;// 持有的被裝飾者private final Cache delegate;// 每個 key 都有其對應的 ReentrantLock鎖對象private final ConcurrentHashMap<Object, ReentrantLock> locks;// 初始化 持有的持有的被裝飾者 和 鎖集合public BlockingCache(Cache delegate) {this.delegate = delegate;this.locks = new ConcurrentHashMap<>();} }

假設 線程 A 在 BlockingCache 中未查找到 keyA 對應的緩存項時,線程 A 會獲取 keyA 對應的鎖,這樣,線程 A 在后續查找 keyA 時,其它線程會被阻塞。

// 根據 key 獲取鎖對象,然后上鎖private void acquireLock(Object key) {// 獲取 key 對應的鎖對象Lock lock = getLockForKey(key);// 獲取鎖,帶超時時長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 {// 上鎖lock.lock();}}private ReentrantLock getLockForKey(Object key) {// Java8 新特性,Map系列類 中新增的方法// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)// 表示,若 key 對應的 value 為空,則將第二個參數的返回值存入該 Map集合 并返回return locks.computeIfAbsent(key, k -> new ReentrantLock());}

假設 線程 A 從數據庫中查找到 keyA 對應的結果對象后,將結果對象放入到 BlockingCache 中,此時 線程 A 會釋放 keyA 對應的鎖,喚醒阻塞在該鎖上的線程。其它線程即可從 BlockingCache 中獲取 keyA 對應的數據,而不是再次訪問數據庫。

@Overridepublic void putObject(Object key, Object value) {try {// 存入 key 和其對應的緩存項delegate.putObject(key, value);} finally {// 最后釋放鎖releaseLock(key);}}private void releaseLock(Object key) {ReentrantLock lock = locks.get(key);// 鎖是否被當前線程持有if (lock.isHeldByCurrentThread()) {// 是,則釋放鎖lock.unlock();}}

FifoCache 和 LruCache

在很多場景中,為了控制緩存的大小,系統需要按照一定的規則清理緩存。FifoCache 是先入先出版本的裝飾器,當向緩存添加數據時,如果緩存項的個數已經達到上限,則會將緩存中最老(即最早進入緩存)的緩存項刪除。

public class FifoCache implements Cache {// 被裝飾對象private final Cache delegate;// 用一個 FIFO 的隊列記錄 key 的順序,其具體實現為 LinkedListprivate final Deque<Object> keyList;// 決定了緩存的容量上限private int size;// 國際慣例,通過構造方法初始化自己的屬性,緩存容量上限默認為 1024個public FifoCache(Cache delegate) {this.delegate = delegate;this.keyList = new LinkedList<>();this.size = 1024;}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size = size;}@Overridepublic void putObject(Object key, Object value) {// 存儲緩存項之前,先在 keyList 中注冊cycleKeyList(key);// 存儲緩存項delegate.putObject(key, value);}private void cycleKeyList(Object key) {// 在 keyList隊列 中注冊要添加的 keykeyList.addLast(key);// 如果注冊這個 key 會超出容積上限,則把最老的一個緩存項清除掉if (keyList.size() > size) {Object oldestKey = keyList.removeFirst();delegate.removeObject(oldestKey);}}@Overridepublic Object getObject(Object key) {return delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}// 除了清理緩存項,還要清理 key 的注冊列表@Overridepublic void clear() {delegate.clear();keyList.clear();} }

LruCache 是按照"近期最少使用算法"(Least Recently Used, LRU)進行緩存清理的裝飾器,在需要清理緩存時,它會清除最近最少使用的緩存項。

public class LruCache implements Cache {// 被裝飾者private final Cache delegate;// 這里使用的是 LinkedHashMap,它繼承了 HashMap,但它的元素是有序的private Map<Object, Object> keyMap;// 最近最少被使用的緩存項的 keyprivate Object eldestKey;// 國際慣例,構造方法中進行屬性初始化public LruCache(Cache delegate) {this.delegate = delegate;// 這里初始化了 keyMap,并定義了 eldestKey 的取值規則setSize(1024);}public void setSize(final int size) {// 初始化 keyMap,同時指定該 Map 的初始容積及加載因子,第三個參數true 表示 該LinkedHashMap// 記錄的順序是 accessOrder,即,LinkedHashMap.get()方法 會改變其中元素的順序keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {private static final long serialVersionUID = 4267176411845948333L;// 當調用 LinkedHashMap.put()方法 時,該方法會被調用@Overrideprotected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {boolean tooBig = size() > size;if (tooBig) {// 當已達到緩存上限,更新 eldestKey字段,后面將其刪除eldestKey = eldest.getKey();}return tooBig;}};}// 存儲緩存項@Overridepublic void putObject(Object key, Object value) {delegate.putObject(key, value);// 記錄緩存項的 key,超出容量則清除最久未使用的緩存項cycleKeyList(key);}private void cycleKeyList(Object key) {keyMap.put(key, key);// eldestKey 不為空,則表示已經達到緩存上限if (eldestKey != null) {// 清除最久未使用的緩存delegate.removeObject(eldestKey);// 制空eldestKey = null;}}@Overridepublic Object getObject(Object key) {// 訪問 key元素 會改變該元素在 LinkedHashMap 中的順序keyMap.get(key); //touchreturn delegate.getObject(key);}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyMap.clear();} }

SoftCache 和 WeakCache

在分析 SoftCache 和 WeakCache 實現之前,我們再溫習一下 Java 提供的 4 種引用類型,強引用 StrongReference、軟引用 SoftReference、弱引用 WeakReference 和虛引用 PhantomReference。

?強引用 平時用的最多的,如 Object obj = new Object(),新建的 Object 對象 就是被強引用的。如果一個對象被強引用,即使是 JVM 內存空間不足,要拋出 OutOfMemoryError 異常,GC 也絕不會回收該對象。?軟引用 僅次于強引用的一種引用,它使用類 SoftReference 來表示。當 JVM 內存不足時,GC 會回收那些只被軟引用指向的對象,從而避免內存溢出。軟引用適合引用那些可以通過其他方式恢復的對象,例如, 數據庫緩存中的對象就可以從數據庫中恢復,所以軟引用可以用來實現緩存,下面要介紹的 SoftCache 就是通過軟引用實現的。
另外,由于在程序使用軟引用之前的某個時刻,其所指向的對象可能己經被 GC 回收掉了,所以通過 Reference.get()方法 來獲取軟引用所指向的對象時,總是要通過檢查該方法返回值是否為 null,來判斷被軟引用的對象是否還存活。?弱引用 弱引用使用 WeakReference 表示,它不會阻止所引用的對象被 GC 回收。在 JVM 進行垃圾回收時,如果指向一個對象的所有引用都是弱引用,那么該對象會被回收。所以,只被弱引用所指向的對象,其生存周期是 兩次 GC 之間 的這段時間,而只被軟引用所指向的對象可以經歷多次 GC,直到出現內存緊張的情況才被回收。?虛引用 最弱的一種引用類型,由類 PhantomReference 表示。虛引用可以用來實現比較精細的內存使用控制,但很少使用。?引用隊列(ReferenceQueue ) 很多場景下,我們的程序需要在一個對象被 GC 時得到通知,引用隊列就是用于收集這些信息的隊列。在創建 SoftReference 對象 時,可以為其關聯一個引用隊列,當 SoftReference 所引用的對象被 GC 時, JVM 就會將該 SoftReference 對象 添加到與之關聯的引用隊列中。當需要檢測這些通知信息時,就可以從引用隊列中獲取這些 SoftReference 對象。不僅是 SoftReference,弱引用和虛引用都可以關聯相應的隊列。

現在來看一下 SoftCache 的具體實現。

public class SoftCache implements Cache {// 這里使用了 LinkedList 作為容器,在 SoftCache 中,最近使用的一部分緩存項不會被 GC// 這是通過將其 value 添加到 hardLinksToAvoidGarbageCollection集合 實現的(即,有強引用指向其value)private final Deque<Object> hardLinksToAvoidGarbageCollection;// 引用隊列,用于記錄已經被 GC 的緩存項所對應的 SoftEntry對象private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;// 持有的被裝飾者private final Cache delegate;// 強連接的個數,默認為 256private int numberOfHardLinks;// 構造方法進行屬性的初始化public SoftCache(Cache delegate) {this.delegate = delegate;this.numberOfHardLinks = 256;this.hardLinksToAvoidGarbageCollection = new LinkedList<>();this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();}private static class SoftEntry extends SoftReference<Object> {private final Object key;SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {// 指向 value 的引用是軟引用,并且關聯了 引用隊列super(value, garbageCollectionQueue);// 強引用this.key = key;}}@Overridepublic void putObject(Object key, Object value) {// 清除已經被 GC 的緩存項removeGarbageCollectedItems();// 添加緩存delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));}private void removeGarbageCollectedItems() {SoftEntry sv;// 遍歷 queueOfGarbageCollectedEntries集合,清除已經被 GC 的緩存項 valuewhile ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {delegate.removeObject(sv.key);}}@Overridepublic Object getObject(Object key) {Object result = null;@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache// 用一個軟引用指向 key 對應的緩存項SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);// 檢測緩存中是否有對應的緩存項if (softReference != null) {// 獲取 softReference 引用的 valueresult = softReference.get();// 如果 softReference 引用的對象已經被 GC,則從緩存中清除對應的緩存項if (result == null) {delegate.removeObject(key);} else {synchronized (hardLinksToAvoidGarbageCollection) {// 將緩存項的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存hardLinksToAvoidGarbageCollection.addFirst(result);// 如果 hardLinksToAvoidGarbageCollection 的容積已經超過 numberOfHardLinks// 則將最老的緩存項從 hardLinksToAvoidGarbageCollection 中清除,FIFOif (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {hardLinksToAvoidGarbageCollection.removeLast();}}}}return result;}@Overridepublic Object removeObject(Object key) {// 清除指定的緩存項之前,也會先清理被 GC 的緩存項removeGarbageCollectedItems();return delegate.removeObject(key);}@Overridepublic void clear() {synchronized (hardLinksToAvoidGarbageCollection) {// 清理強引用集合hardLinksToAvoidGarbageCollection.clear();}// 清理被 GC 的緩存項removeGarbageCollectedItems();// 清理最底層的緩存項delegate.clear();}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {removeGarbageCollectedItems();return delegate.getSize();}public void setSize(int size) {this.numberOfHardLinks = size;} }

WeakCache 的實現與 SoftCache 基本類似,唯一的區別在于其中使用 WeakEntry(繼承了 WeakReference)封裝真正的 value 對象,其他實現完全一樣。

另外,還有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理緩存的裝飾器,它的 clearInterval 字段 記錄了兩次緩存清理之間的時間間隔,默認是一小時,lastClear 字段 記錄了最近一次清理的時間戳。ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在執行時都會根據這兩個字段檢測是否需要進行清理操作,清理操作會清空緩存中所有緩存項。

LoggingCache 在 Cache 的基礎上提供了日志功能,它通過 hit 字段 和 request 字段 記錄了 Cache 的命中次數和訪問次數。在 LoggingCache.getObject()方法 中,會統計命中次數和訪問次數 這兩個指標,井按照指定的日志輸出方式輸出命中率。

SynchronizedCache 通過在每個方法上添加 synchronized 關鍵字,為 Cache 添加了同步功能,有點類似于 JDK 中 Collections 的 SynchronizedCollection 內部類。

SerializedCache 提供了將 value 對象 序列化的功能。SerializedCache 在添加緩存項時,會將 value 對應的 Java 對象 進行序列化,井將序列化后的 byte[]數組 作為 value 存入緩存 。SerializedCache 在獲取緩存項時,會將緩存項中的 byte[]數組 反序列化成 Java 對象。不使用 SerializedCache 裝飾器 進行裝飾的話,每次從緩存中獲取同一 key 對應的對象時,得到的都是同一對象,任意一個線程修改該對象都會影響到其他線程,以及緩存中的對象。而使用 SerializedCache 每次從緩存中獲取數據時,都會通過反序列化得到一個全新的對象。SerializedCache 使用的序列化方式是 Java 原生序列化。

2、CacheKey

在 Cache 中唯一確定一個緩存項,需要使用緩存項的 key 進行比較,MyBatis 中因為涉及 動態 SQL 等多方面因素, 其緩存項的 key 不能僅僅通過一個 String 表示,所以 MyBatis 提供了 CacheKey 類 來表示緩存項的 key,在一個 CacheKey 對象 中可以封裝多個影響緩存項的因素。CacheKey 中可以添加多個對象,由這些對象共同確定兩個 CacheKey 對象 是否相同。

public class CacheKey implements Cloneable, Serializable {private static final long serialVersionUID = 1146682552656046210L;public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();private static final int DEFAULT_MULTIPLYER = 37;private static final int DEFAULT_HASHCODE = 17;// 參與計算hashcode,默認值DEFAULT_MULTIPLYER = 37private final int multiplier;// 當前CacheKey對象的hashcode,默認值DEFAULT_HASHCODE = 17private int hashcode;// 校驗和private long checksum;private int count;// 由該集合中的所有元素 共同決定兩個CacheKey對象是否相同,一般會使用一下四個元素// MappedStatement的id、查詢結果集的范圍參數(RowBounds的offset和limit)// SQL語句(其中可能包含占位符"?")、SQL語句中占位符的實際參數private List<Object> updateList;// 構造方法初始化屬性public CacheKey() {this.hashcode = DEFAULT_HASHCODE;this.multiplier = DEFAULT_MULTIPLYER;this.count = 0;this.updateList = new ArrayList<>();}public CacheKey(Object[] objects) {this();updateAll(objects);}public void update(Object object) {int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);// 重新計算count、checksum和hashcode的值count++;checksum += baseHashCode;baseHashCode *= count;hashcode = multiplier * hashcode + baseHashCode;// 將object添加到updateList集合updateList.add(object);}public int getUpdateCount() {return updateList.size();}public void updateAll(Object[] objects) {for (Object o : objects) {update(o);}}/*** CacheKey重寫了 equals() 和 hashCode()方法,這兩個方法使用上面介紹* 的 count、checksum、hashcode、updateList 比較兩個 CacheKey對象 是否相同*/@Overridepublic boolean equals(Object object) {// 如果為同一對象,直接返回 trueif (this == object) {return true;}// 如果 object 都不是 CacheKey類型,直接返回 falseif (!(object instanceof CacheKey)) {return false;}// 類型轉換一下final CacheKey cacheKey = (CacheKey) object;// 依次比較 hashcode、checksum、count,如果不等,直接返回 falseif (hashcode != cacheKey.hashcode) {return false;}if (checksum != cacheKey.checksum) {return false;}if (count != cacheKey.count) {return false;}// 比較 updateList 中的元素是否相同,不同直接返回 falsefor (int i = 0; i < updateList.size(); i++) {Object thisObject = updateList.get(i);Object thatObject = cacheKey.updateList.get(i);if (!ArrayUtil.equals(thisObject, thatObject)) {return false;}}return true;}@Overridepublic int hashCode() {return hashcode;}@Overridepublic String toString() {StringJoiner returnValue = new StringJoiner(":");returnValue.add(String.valueOf(hashcode));returnValue.add(String.valueOf(checksum));updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);return returnValue.toString();}@Overridepublic CacheKey clone() throws CloneNotSupportedException {CacheKey clonedCacheKey = (CacheKey) super.clone();clonedCacheKey.updateList = new ArrayList<>(updateList);return clonedCacheKey;} }

總結

以上是生活随笔為你收集整理的apache缓存清理_深挖 Mybatis 源码:缓存模块的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。