Java中的CopyOnWriteArrayList容器解析
1. 簡介
在 ArrayList 的類注釋上,JDK 就提醒了我們,如果要把 ArrayList 作為共享變量的話,是線程不安全的,推薦我們自己加鎖或者使用 Collections.synchronizedList 方法,其實 JDK 還提供了另外一種線程安全的 List,叫做 CopyOnWriteArrayList
2. 原理
很多時候,我們的系統(tǒng)應對的都是讀多寫少的并發(fā)場景。CopyOnWriteArrayList容器允許并發(fā)讀,讀操作是無鎖的,性能較高。至于寫操作,比如向容器中添加一個元素,則首先將當前容器復制一份,然后在新副本上執(zhí)行寫操作,結束之后再將原容器的引用指向新容器。
- 線程安全的,多線程環(huán)境下可以直接使用,無需加鎖;
- 通過鎖 + 數(shù)組拷貝 + volatile 關鍵字保證了線程安全;
- 每次數(shù)組操作,都會把數(shù)組拷貝一份出來,在新數(shù)組上進行操作,操作成功之后再賦值回去。
從整體架構上來說,CopyOnWriteArrayList 數(shù)據(jù)結構和 ArrayList 是一致的,底層是個數(shù)組,只不過 CopyOnWriteArrayList 在對數(shù)組進行操作的時候,基本會分四步走:
- 加鎖;
- 從原數(shù)組中拷貝出新數(shù)組;
- 在新數(shù)組上進行操作,并把新數(shù)組賦值給數(shù)組容器;
- 解鎖
除了加鎖之外,CopyOnWriteArrayList 的底層數(shù)組還被 volatile 關鍵字修飾,意思是一旦數(shù)組被修改,其它線程立馬能夠感知到,代碼如下:
private transient volatile Object[] array;
整體上來說,CopyOnWriteArrayList 就是利用鎖 + 數(shù)組拷貝 + volatile 關鍵字保證了 List 的線程安全。
3. 優(yōu)點
讀操作(不加鎖)性能很高,因為無需任何同步措施,比較適用于讀多寫少的并發(fā)場景。Java的list在遍歷時,若中途有別的線程對list容器進行修改,則會拋ConcurrentModificationException異常。而CopyOnWriteArrayList由于其"讀寫分離"的思想,遍歷和修改操作分別作用在不同的list容器,所以在使用迭代器進行遍歷時候,也就不會拋出ConcurrentModificationException異常了。
4. 缺點
一是內存占用問題,畢竟每次執(zhí)行寫操作都要將原容器拷貝一份。數(shù)據(jù)量大時,對內存壓力較大,可能會引起頻繁GC;
二是無法保證實時性,因為CopyOnWrite的寫時復制機制,所以在進行寫操作的時候,內存里會同時駐扎兩個對象的內存,舊的對象和新寫入的對象(注意:在復制的時候只是復制容器里的引用,只是在寫的時候會創(chuàng)建新對象添加到新容器里,而舊容器的對象還在使用,所以有兩份對象內存)。
5. 源碼分析
5.1 添加操作
public boolean add(E e) { //ReentrantLock加鎖,保證線程安全 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; //拷貝原容器,長度為原容器長度加一 Object[] newElements = Arrays.copyOf(elements, len + 1); //在新副本上執(zhí)行添加操作 newElements[len] = e; //將原容器引用指向新副本 setArray(newElements); return true; } finally { //解鎖 lock.unlock(); } }
添加的邏輯很簡單,先將原容器copy一份,然后在新副本上執(zhí)行寫操作,之后再切換引用。當然此過程是要加鎖的。
5.2 刪除操作
public E remove(int index) { //加鎖 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) //如果要刪除的是列表末端數(shù)據(jù),拷貝前l(fā)en-1個數(shù)據(jù)到新副本上,再切換引用 setArray(Arrays.copyOf(elements, len - 1)); else { //否則,將除要刪除元素之外的其他元素拷貝到新副本中,并切換引用 Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { //解鎖 lock.unlock(); } }
刪除操作同理,將除要刪除元素之外的其他元素拷貝到新副本中,然后切換引用,將原容器引用指向新副本。同屬寫操作,需要加鎖。
我們再來看看讀操作,CopyOnWriteArrayList的讀操作是不用加鎖的,性能很高。
public E get(int index) { return get(getArray(), index); }
直接讀取即可,無需加鎖
private E get(Object[] a, int index) { return (E) a[index]; }
5.3 弱一致性的迭代器
所謂弱一致性是指返回迭代器后,其他線程對list的增刪改查對迭代器是不可見的
// 演示多線程下迭代器的弱一致性結果 public class copylist { private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>(); public static void main(String[] args) throws InterruptedException { arrayList.add("hello"); arrayList.add("alibaba"); arrayList.add("welcome"); arrayList.add("to"); arrayList.add("hangzhou"); Thread threadOne = new Thread(new Runnable() { @Override public void run() { // 修改list中下標為1的元素為ali arrayList.set(1, "ali"); // 刪除元素 arrayList.remove(2); arrayList.remove(3); } }); // 保證在修改線程啟動前獲取迭代器 Iterator<String> itr = arrayList.iterator(); // 啟動線程 threadOne.start(); // 等待子線程執(zhí)行完畢 threadOne.join(); while(itr.hasNext()) { System.out.println(itr.next()); } } }
執(zhí)行程序:
hello
alibaba
welcome
to
hangzhou
Process finished with exit code 0
從輸出結果我們知道,在子線程里面進行的操作一個都沒有生效,這就是迭代器弱一致性的體現(xiàn)。需要注意的是,獲取迭代器的操作必須在子線程操作之前進行。
6. ArrayList轉為線程安全的方法
List list = Collections.synchronizedList(new ArrayList());
到此這篇關于Java中的CopyOnWriteArrayList容器解析的文章就介紹到這了,更多相關CopyOnWriteArrayList解析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Schedule Task動態(tài)改寫Cron配置方式
這篇文章主要介紹了Spring Schedule Task動態(tài)改寫Cron配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11maven profile自動切換環(huán)境參數(shù)的2種方法詳解
這篇文章主要給大家介紹了關于maven profile自動切換環(huán)境參數(shù)的2種方法,文中通過示例代碼將這兩種方法介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-04-04詳解Spring Cloud Zuul中路由配置細節(jié)
本篇文章主要介紹了詳解Spring Cloud Zuul中路由配置細節(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10