天天看點

面試 | 面試官問題 你知道 CopyOnWriteArrayList 嗎?

面試 | 面試官問題 你知道 CopyOnWriteArrayList 嗎?

寫入時複制(CopyOnWrite)思想

寫入時複制(CopyOnWrite,簡稱COW)思想是計算機程式設計領域中的一種優化政策。其核心思想是,如果有多個調用者(Callers)同時要求相同的資源(如記憶體或者是磁盤上的資料存儲),他們會共同擷取相同的指針指向相同的資源,直到某個調用者視圖修改資源内容時,系統才會真正複制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此做法主要的優點是如果調用者沒有修改資源,就不會有副本(private copy)被建立,是以多個調用者隻是讀取操作時可以共享同一份資源。

在使用CopyOnWriteArrayList之前,我們先閱讀其源碼了解下它是如何實作的。以下代碼是向CopyOnWriteArrayList中add方法的實作(向CopyOnWriteArrayList裡添加元素),可以發現在添加的時候是需要加鎖的,否則多線程寫的時候會Copy出N個副本出來。

讀的時候不需要加鎖,如果讀的時候有多個線程正在向CopyOnWriteArrayList添加資料,讀還是會讀到舊的資料,因為寫的時候不會鎖住舊的CopyOnWriteArrayList。

JDK中并沒有提供CopyOnWriteMap,我們可以參考CopyOnWriteArrayList來實作一個,基本代碼如下:

實作很簡單,隻要了解了CopyOnWrite機制,我們可以實作各種CopyOnWrite容器,并且在不同的應用場景中使用。

實作了List接口

内部持有一個ReentrantLock lock = new ReentrantLock();

底層是用volatile transient聲明的數組 array

讀寫分離,寫時複制出一個新的數組,完成插入、修改或者移除操作後将新數組指派給array

** volatile (揮發物、易變的)** :變量修飾符,隻能用來修飾變量。volatile修飾的成員變量在每次被線程通路時,都強迫從共享記憶體中重讀該成員變量的值。而且,當成員變量發生變 化時,強迫線程将變化值回寫到共享記憶體。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

面試 | 面試官問題 你知道 CopyOnWriteArrayList 嗎?

image

面試 | 面試官問題 你知道 CopyOnWriteArrayList 嗎?

1)增

2)删

3)改

4)查

CopyOnWrite并發容器用于讀多寫少的并發場景。比如白名單,黑名單,商品類目的通路和更新場景,假如我們有一個搜尋網站,使用者在這個網站的搜尋框中,輸入關鍵字搜尋内容,但是某些關鍵字不允許被搜尋。這些不能被搜尋的關鍵字會被放在一個黑名單當中,黑名單每天晚上更新一次。當使用者搜尋時,會檢查目前關鍵字在不在黑名單當中,如果在,則提示不能搜尋。實作代碼如下:

代碼很簡單,但是使用CopyOnWriteMap需要注意兩件事情:

1. 減少擴容開銷。根據實際需要,初始化CopyOnWriteMap的大小,避免寫時CopyOnWriteMap擴容的開銷。

2. 使用批量添加。因為每次添加,容器每次都會進行複制,是以減少添加次數,可以減少容器的複制次數。如使用上面代碼裡的addBlackList方法。

CopyOnWrite容器有很多優點,但是同時也存在兩個問題,即記憶體占用問題和資料一緻性問題。是以在開發的時候需要注意一下。

「記憶體占用問題」。因為CopyOnWrite的寫時複制機制,是以在進行寫操作的時候,記憶體裡會同時駐紮兩個對象的記憶體,舊的對象和新寫入的對象(注意:在複制的時候隻是複制容器裡的引用,隻是在寫的時候會建立新對象添加到新容器裡,而舊容器的對象還在使用,是以有兩份對象記憶體)。如果這些對象占用的記憶體比較大,比如說200M左右,那麼再寫入100M資料進去,記憶體就會占用300M,那麼這個時候很有可能造成頻繁的Yong GC和Full GC。之前我們系統中使用了一個服務由于每晚使用CopyOnWrite機制更新大對象,造成了每晚15秒的Full GC,應用響應時間也随之變長。

「針對記憶體占用問題」,可以通過壓縮容器中的元素的方法來減少大對象的記憶體消耗,比如,如果元素全是10進制的數字,可以考慮把它壓縮成36進制或64進制。或者不使用CopyOnWrite容器,而使用其他的并發容器,如ConcurrentHashMap。

「資料一緻性問題」。CopyOnWrite容器隻能保證資料的最終一緻性,不能保證資料的實時一緻性。是以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。

我知道Vector是增删改查方法都加了synchronized,保證同步,但是每個方法執行的時候都要去獲得鎖,性能就會大大下降,而CopyOnWriteArrayList 隻是在增删改上加鎖,但是讀不加鎖,在讀方面的性能就好于Vector,CopyOnWriteArrayList支援讀多寫少的并發情況。

面試 | 面試官問題 你知道 CopyOnWriteArrayList 嗎?

繼續閱讀