日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java8 CopyOnWriteArrayList 源码分析

發(fā)布時間:2024/9/30 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java8 CopyOnWriteArrayList 源码分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、CopyOnWriteArrayList 概述

1.1 概念概述

CopyOnWriteArrayList 是 juc 包下一個線程安全的并發(fā)容器,底層使用數(shù)組實現(xiàn)。CopyOnWrite 顧名思義是寫時復(fù)制的意思,其基本思路是,從一開始大家都在共享同一個內(nèi)容,當(dāng)某個人想要修改這個內(nèi)容的時候,會把內(nèi)容 Copy 出去形成一個新的內(nèi)容然后再進(jìn)行修改,這是一種延時懶惰策略。

假設(shè)往一個容器添加元素的時候,不直接往當(dāng)前容器添加,而是加鎖后先將當(dāng)前容器進(jìn)行 Copy,復(fù)制出一個新的容器,然后在新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器,最后釋放鎖。這樣做的好處是我們可以對 CopyOnWrite 容器進(jìn)行并發(fā)的讀,而不需要加鎖,只有在修改時才加鎖,從而達(dá)到讀寫分離的效果。

1.2 特性

  • 每次對數(shù)組中的元素進(jìn)行修改時都會創(chuàng)建一個新數(shù)組,因此沒有擴容機制
  • 讀元素不加鎖,修改元素時才加鎖
  • 允許存儲 null 元素
  • CopyOnWriteArraySet 底層原理使用的是 CopyOnWriteArrayList
  • CopyOnWriteArrayList 使用與讀多寫少的場景,如果寫場景比較多的場景下比較消耗內(nèi)存

1.3 圖解

下面是一個 CopyOnWriteArrayList 的原理圖:

二、源碼分析

2.1 內(nèi)部屬性與相關(guān)方法

/*** 顯示鎖對象*/final transient ReentrantLock lock = new ReentrantLock();/*** 內(nèi)部底層數(shù)組,volatile 關(guān)鍵字修飾*/private transient volatile Object[] array;/*** 返回內(nèi)部 array*/final Object[] getArray() {return array;}/*** 設(shè)置 array*/final void setArray(Object[] a) {array = a;}

CopyOnWriteArrayList 寫鎖使用的是 ReentrantLock,底層是一個被 volatile 關(guān)鍵字修飾的數(shù)組。構(gòu)造函數(shù)相對來說也比較簡單,就不介紹了,有興趣的可以自己查看下。

2.2 add 方法

public boolean add(E e) {// 加鎖final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;// 把原數(shù)組中的元素拷貝到新數(shù)組中,并使數(shù)組容量 + 1Object[] newElements = Arrays.copyOf(elements, len + 1);// 新元素添加到新數(shù)組中newElements[len] = e;// 重新賦值 arraysetArray(newElements);return true;} finally {// 釋放鎖lock.unlock();}}

源碼很容易理解,添加元素的時候,創(chuàng)建一個新數(shù)組,長度為原數(shù)組長度加 1,將原數(shù)組中的元素拷貝到新數(shù)組中,在新數(shù)組中插入元素即可。

2.3 get 方法

public E get(int index) {return get(getArray(), index);}private E get(Object[] a, int index) {return (E) a[index];}

注意:獲取元素的方法是不需要加鎖的。

2.4 remove 方法

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;// 如果移除的是數(shù)組中最后一個元素,復(fù)制元素時直接舍棄最后一個if (numMoved == 0)setArray(Arrays.copyOf(elements, len - 1));else {// 初始化新數(shù)組為原數(shù)組長度減 1Object[] newElements = new Object[len - 1];// 分兩次完成元素拷貝System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);// 重置 arraysetArray(newElements);}// 返回老 valuereturn oldValue;} finally {// 釋放鎖lock.unlock();}}

刪除指定位置上的元素時會創(chuàng)建一個原數(shù)組長度減 1 的新數(shù)組,然后以 index 索引為標(biāo)記,分兩次拷貝,將元素拷貝到新數(shù)組中。

2.5 addIfAbsent 方法

總的來說,CopyOnWriteArrayList 內(nèi)部的方法都比較容易理解,相對比較難理解的有兩個方法:

  • remove(Object o):移除指定的元素值(只會移除第一次出現(xiàn)的)
  • addIfAbsent(E e):元素不存在的條件下才添加

這兩個方法都涉及到了快照(snapshot)對象,下面是具體代碼:

public boolean addIfAbsent(E e) {// 因為這里不加鎖,因此使用快照對象Object[] snapshot = getArray();// 從頭開始查找,如果該元素已存在,則返回 false,不存在才添加return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot);}

indexof 方法用于返回指定元素在原數(shù)組中的位置,不存在返回 -1,下面是源代碼:

private static int indexOf(Object o, Object[] elements,int index, int fence) {// null 與非 null 元素走不同的查找邏輯if (o == null) {for (int i = index; i < fence; i++)if (elements[i] == null)return i;} else {for (int i = index; i < fence; i++)if (o.equals(elements[i]))return i;}// 沒有該元素返回 -1return -1;}

接下來就是一個比較高能的方法了:

private boolean addIfAbsent(E e, Object[] snapshot) {// 加鎖final ReentrantLock lock = this.lock;lock.lock();try {// 獲取當(dāng)前數(shù)組Object[] current = getArray();int len = current.length;/*** 并發(fā)環(huán)境下造成的不一致情況* 因為獲取快照數(shù)組的時候沒有加鎖,別的線程可能修改了原數(shù)組,* 就會造成快照數(shù)組與原數(shù)組不一致*/if (snapshot != current) {// Optimize for lost race to another addXXX operation// 獲取較小的數(shù)組長度int common = Math.min(snapshot.length, len);// 可以分兩種情況考慮,1 添加的時候刪除了元素 2 一直添加元素for (int i = 0; i < common; i++)// 如果添加的元素在原數(shù)組中存在,則結(jié)束循環(huán)if (current[i] != snapshot[i] && eq(e, current[i]))return false;// 和上面的 for 循環(huán)正好組成一個數(shù)組大小,用于判斷原數(shù)組中是否已經(jīng)存在添加的元素if (indexOf(e, current, common, len) >= 0)return false;}// 要添加的元素在原數(shù)組中不存在Object[] newElements = Arrays.copyOf(current, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {// 釋放鎖lock.unlock();}}

在并發(fā)情況下,因為獲取的快照數(shù)組與原數(shù)組可能不一致(假如別的線程已經(jīng)新增了元素),因此需要重新判斷添加的元素是否已經(jīng)存在。

??:大家可能會問直接加鎖不就解決問題了嗎,為什么非要搞一個快照文件呢?搞得這么麻煩,因為使用快照數(shù)組在訪問非常頻繁的情況下可以提升一點性能。另一個并發(fā)容器 CopyOnWriteArrayList 底層就是用 CopyOnWriteArrayList 存儲元素,它的添加方法調(diào)用的就是 addIfAbsent 方法,這樣就不難理解為什么要這么做了。

另外一個方法 remove(Object o) 也用到了快照數(shù)組,比 addIfAbsent 稍微難理解一點,有興趣的可以自己查看。

jdk1.8 源碼閱讀:https://github.com/zchen96/jdk1.8-source-code-read

參考

http://ifeve.com/java-copy-on-write/

總結(jié)

以上是生活随笔為你收集整理的Java8 CopyOnWriteArrayList 源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。