一、場景
當我們在多執行緒的環境下操作一個集合,比如 ArrayList或者Hashmap,這些集合默認情況下肯定是執行緒不安全的,如果說多個執行緒同時去讀和寫這些集合就會有執行緒安全問題,
好,問題來了,我們應該怎么讓一個集合變成執行緒安全的呢?
二、synchronized 或者lock鎖
有一個非常簡單的辦法,對這些集合的訪問都加上執行緒同步的控制,或者說是加鎖,
這里可以去另一篇文章看一下Synchronized和lock鎖的原理簡述synchronized和lock鎖
最簡單的做法就是加一個Synchronized或者Lock鎖,
我們假設就是用 ReadWriteLock 讀寫鎖的方式來控制對這些集合的訪問,
這樣多個讀請求可以同時執行從這些集合里讀取資料,但是讀請求和寫請求之間互斥,寫請求和寫請求也是互斥的,
public Object read() {
lock.readLock().lock();
// 對集合的讀操作
lock.readLock().unlock();
}
public void write() {
lock.writeLock().lock();
// 對集合的寫操作
lock.writeLock().unlock();
}
大家想想,類似上面的代碼有什么問題呢?
最大的問題,其實就在于寫鎖和讀鎖的互斥,假設寫操作頻率很低,讀操作頻率很高,是寫少讀多的場景,
那么偶爾執行一個寫操作的時候,是不是會加上寫鎖,此時大量的讀操作過來是不是就會被阻塞住,無法執行?
這個就是讀寫鎖可能遇到的最大的問題,
三、從kafka原始碼獲取靈感
kafka實作了一個 CopyOnWriteMap解決了上面的一系列問題,這個 CopyOnWriteMap采用的是CopyOnWrite思想,它是一種類似于讀寫分離的思想,
我們來看一下這個 CopyOnWriteMap 的原始碼實作:
// 典型的volatile修飾普通Map
private volatile Map map;
@Override
public synchronized V put(K k, V v) {
// 更新的時候先創建副本,更新副本,然后對volatile變數賦值寫回去
Map copy = new HashMap(this.map);
V prev = copy.put(k, v);
this.map = Collections.unmodifiableMap(copy);
return prev;
}
@Override
public V get(Object k) {
// 讀取的時候直接讀volatile變數參考的map資料結構,無需鎖
return map.get(k);
}
如果你是寫操作(put)的話,它會先去創建一個副本,然后對這個副本用syn鎖,這樣就能確保一次只能有一個執行緒去修改這個副本,而讀操作(get),直接不加鎖,因為不管多少個執行緒讀,對資料都沒有影響,這樣就既能保證讀寫的執行緒安全,又能極大提升性能!
而他這里最精妙的地方在于,如何確保你讀到的都是最新的資料呢?他這里使用了volatile修飾這個map,volatile是確保多執行緒環境下的可見性問題,那么當我們寫操作更新了這個副本,它就會馬上更新我們讀的那個資料,這樣就解決了讀寫分離最難的同步資料問題!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/327915.html
標籤:其他
上一篇:京淘專案——專案發布+安裝JDK+安裝MariaDB資料庫+防火墻
下一篇:資料清洗有哪些方法?
