
介紹
我原來遇到這樣一種場景,我們將一些配置資訊存在資料庫中,但這種配置資訊訪問的頻率非常高,如果每次從資料庫中查詢,會明顯降低效率,后來我就在每次啟動專案的時候把資料庫中的資料加載到本地快取中,當配置發生變化時同步更新快取
本地快取結構如下
Map<String, List<Integer>> cache = new ConcurrentHashMap<>();
這個快取有什么問題呢?
如果這個List的實作類是ArrayList,那么可能會發生執行緒安全問題
如果List的實作類是Collections.synchronizedList或者Vector,還會有問題嗎?
其實還是有問題的,因為不管ArrayList,Collections.synchronizedList還是Vector,在迭代期間是不允許編輯的,如果在迭代期間進行添加或者洗掉等操作,則會拋出ConcurrentModificationException,適用起來還是不方便
還有哪些List介面的實作類能同時解決上面的兩種問題呢?
答案就是CopyOnWriteArrayList和CopyOnWriteArraySet,其實CopyOnWriteArrayList和CopyOnWriteArraySet背后的原理就是Copy-on-Write(寫時復制),即在容器中的元素被修改時,復制陣列,在復制的陣列上做修改,當修改完畢用復制的陣列替代舊的陣列
從原始碼看一下具體實作
讀取陣列內容
讀取陣列內容不需要加鎖
// CopyOnWriteArrayList
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
修改陣列內容
整體思想就是加鎖,復制新陣列,在新陣列上進行修改,修改完畢再用新陣列替換舊陣列
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
在迭代期間,如果需要修改元素,此時修改的是復制的陣列,而迭代的原來的資料,所以不會發生ConcurrentModificationException,但是讀取的時候可能會發生資料不一致的情況,因為讀和寫分別操作的是2個陣列
@Test
public void cowTest() {
List<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1, 2, 3});
System.out.println(list);
Iterator it1 = list.iterator();
list.add(4);
System.out.println(list);
Iterator it2 = list.iterator();
System.out.println("---it1---");
it1.forEachRemaining(System.out::println);
System.out.println("---it2---");
it2.forEachRemaining(System.out::println);
}
適用場景
- 業務場景讀多寫少
- 對資料一致性要求不高可以考慮使用COW容器
參考博客
[1]https://www.cnblogs.com/dolphin0520/p/3933551.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/263028.html
標籤:其他
下一篇:Java基礎面試題
