為什麼還需要使用讀寫分離的Map?
首先我們先介紹一下常用的Map
- 線程不安全
- HashMap : 非線程安全,即任一時刻可以由多個線程同時寫HashMap,可能會導致數據不一致。
- LinkedListHashMap: 非線程安全,保證 加入的順序是怎麼,循環出來就是什麼順序。可以理解為維護了元素次序的HashMap。
- TreeMap:有序的集合,可以實現自定義排序。
2.線程安全
- HashTable : 線程安全,但是讀和寫都需要加鎖,性能比較低。
- ConcurrentHashMap: 採用分段鎖實現,但是同一段的寫和讀也是互斥的,所以性能稍微低
那麼如何實現一個緩存呢,可以進行併發的讀和寫的,從上面介紹類看,
無論是HashTable 還是 ConcurrentHashMap 都會遇到讀加鎖的情況,所以不太適合這種場景。
那麼我們下面實現可以進行讀寫分離的Map,即寫加鎖,讀不加鎖。
CopyOnWriteMap 的實現
1、實現步驟如下
- 定義一個由初始容量的map.並使用volatile 修飾,這個為了保證在多線程環境下可見性。
- 定義一個 可重入鎖 ReentrantLock
- 定義 put 方法,putAll方法,get方法
2、代碼實現
3、說明
- put 方法
- 首先加鎖
- new 一個新的Map,然後把舊的map放進去
- 在新的Map中添加元素
- 將舊map 的引用指向 新的 map
- 釋放鎖
- get 方法
- get 方法不需要獲取鎖,直接獲取即可。
使用場景
假如有下面一個場景,一個比較小的電商網站(商品信息比較少),所以我們可以直接緩存在內存中,
後期添加、刪除、更新都會更新內存中的緩存。
實現代碼如下:
使用CopyOnWriteMap需要的注意事項:
1、儘量批量添加,否則每次添加都會複製一個map容器,可能會造成頻繁的GC(垃圾回收)。
2、儘量初始化時指定容量
CopyOnWriteMap 優缺點
- 優點
- 實現讀寫分離,讀不加鎖,寫加鎖,性能比較高,適合在高併發場景下使用。
- 對比HashTable 和 ConcurrentHashMap ,CopyOnWriteMap 優勢在於讀不加鎖。併發高。
- 缺點
- 內存佔用高的問題。
- 對寫入的數據不能及時讀出來
下面說明為什麼內存佔用高:
<code>\tpublic V put(K key, V value) {
\t\ttry {
\t\t\tlock.lock();
\t\t\tMapnewMap = new HashMap<>(map); /<code>
\t\t\tV v = newMap.put(key, value);
\t\t\tmap = newMap;
\t\t\treturn v;
\t\t} catch (Exception e) {
\t\t}finally {
\t\t\tlock.unlock();
\t\t}
\t\treturn null;
\t}
從上面看,put操作首先 創建一個新的map,然後把添加的元素添加到新map中,
最後把舊的 map引用指向新的map內存地址。
示意圖如下:
執行 map = newMap; 代碼後:
上面舊的map引用雖然指向了新的Map,但是舊的Map如果回收不及時,會造成內存溢出。
為什麼要使用COW ?
java util 下的集合類都不能應付併發的修改和添加,比如在迭代集合的時候對此集合進行刪除,
則會出現 ConcurrentModificationException異常,或者 多個線程併發的添加,
會出現 上一個添加的值被下一個值覆蓋。
為了應付併發的修改,有以下兩種辦法
1、寫時複製
2、線程安全的容器
比如: ArrayList --> CopyOnWriteList
HashSet --> CopyOnWriteSet
HashMap --> ConcurrentHashMap
閱讀更多 碼農的一天 的文章