1、Copy-On-Write
是什麼?
首先我講一下什麼是Copy-On-Write
,顧名思義,在計算機中就是當你想要對一塊記憶體進行修改時,我們不在原有記憶體塊中進行寫
操作,而是將記憶體拷貝一份,在新的記憶體中進行寫
操作,寫
完之後呢,就將指向原來記憶體指標指向新的記憶體,原來的記憶體就可以被回收掉嘛!
網上兄弟們說了,這是一種用於程式設計中的優化策略
,是一種延時懶惰策略
。都說優化優化,那麼到底優化了哪些問題呢?
先給大家一份程式碼:
public class IteratorTest {
private static List<String> list = new ArrayList<>();
public static void main(String[] args) {
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iter = list.iterator();
//我當前正在迭代集合(這裡模擬併發中讀取某一list的場景)
while (iter.hasNext()) {
System.err.println(iter.next());
}
System.err.println(Arrays.toString(list.toArray()));
}
}
複製程式碼
上面的程式片段在單執行緒下執行時沒什麼毛病的,但到了多執行緒的環境中,可能就GG了!為什麼呢?因為多執行緒環境中,你在迭代的時候是不允許有其他執行緒對這個集合list進行新增元素的,看下面這段程式碼,你會發現丟擲java.util.ConcurrentModificationException
的異常。
public class IteratorTest {
private static List<String> list = new ArrayList<>();
public static void main(String[] args) {
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iter = list.iterator();
// 存放10個執行緒的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);
// 執行10個任務(我當前正在迭代集合(這裡模擬併發中讀取某一list的場景))
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
while (iter.hasNext()) {
System.err.println(iter.next());
}
}
});
}
// 執行10個任務
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
list.add("121");// 新增資料
}
});
}
System.err.println(Arrays.toString(list.toArray()));
}
}
複製程式碼
- 1、這裡的
迭代
表示我當前正在讀取某種集合
中的資料,屬於讀
操作; - 2、執行緒則模擬當前程式處於多執行緒環境中,有其他執行緒正在修改該資料
這裡暴露的問題是什麼呢?
- 1、多執行緒會對迭代集合產生影響,影響讀操作
解決:
- 1、
CopyOnWriteArrayList
避免了多執行緒操作List執行緒不安全的問題
2、CopyOnWriteArrayList
介紹
從JDK1.5開始Java併發包裡提供了兩個使用CopyOnWrite
機制實現的併發容器,它們是CopyOnWriteArrayList
和CopyOnWriteArraySet
。CopyOnWrite
容器非常有用,可以在非常多的併發場景中使用到。
CopyOnWriteArrayList
原理:
上面已經講了,就是在寫的時候不對原集合進行修改,而是重新複製一份,修改完之後,再移動指標
複製程式碼
那麼你可能會問?就算是對原集合進行復制,在多執行緒環境中不也是一樣會導致寫入衝突嗎?沒錯,但是你可能還不知道CopyOnWriteArrayList
中增加刪除元素的實現細節,下面我就說說網上老是提到的add()方法
3、CopyOnWriteArrayList
簡單原始碼解讀
add()
方法原始碼:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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);//將引用指向新陣列 1
return true;
} finally {
lock.unlock();//解鎖啦
}
}
複製程式碼
恍然大悟,小樣,原來add()
在新增集合的時候加上了鎖,保證了同步,避免了多執行緒寫的時候會Copy出N個副本出來。(想想,你在遍歷一個10個元素的集合,每遍歷一次有1人呼叫add方法,你說當你遍歷10次,這add方法是不是得被呼叫10次呢?是不是得copy出10分新集合呢?萬一這個集合非常大呢?
)
那麼?你還要問?CopyOnWriteArrayList
是怎麼解決執行緒安全問題的?答案就是----寫時複製,加鎖
還要問?那麼有沒有這麼一種情況,當一個執行緒剛好呼叫完add()
方法,也就是剛好執行到上面1
處的程式碼,也就是剛好將引用指向心陣列,而此時有執行緒正在遍歷呢?會不會報錯呢?(答案是不會的,因為你正在遍歷的集合是舊的,這就有點難受啦,哈哈~
)
當你把上面的程式碼的ArrayList
改為CopyOnWriteArrayList
,執行就不會報錯啦!
public class IteratorTest {
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iter = list.iterator();
// 存放10個執行緒的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);
// 執行10個任務(我當前正在迭代集合(這裡模擬併發中讀取某一list的場景))
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
while (iter.hasNext()) {
System.err.println(iter.next());
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
list.add("121");// 新增資料
}
});
}
// 執行10個任務
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
list.add("121");// 新增資料
}
});
service.execute(new Runnable() {
@Override
public void run() {
while (iter.hasNext()) {
System.err.println(iter.next());
}
}
});
}
System.err.println(Arrays.toString(list.toArray()));
}
}
複製程式碼
4、CopyOnWriteArrayList
優缺點
缺點:
- 1、耗記憶體(集合複製)
- 2、實時性不高
優點:
- 1、資料一致性完整,為什麼?因為加鎖了,併發資料不會亂
- 2、解決了
像ArrayList
、Vector
這種集合多執行緒遍歷迭代問題,記住,Vector
雖然執行緒安全,只不過是加了synchronized
關鍵字,迭代問題完全沒有解決!
5、CopyOnWriteArrayList
使用場景
- 1、讀多寫少(白名單,黑名單,商品類目的訪問和更新場景),為什麼?因為寫的時候會複製新集合
- 2、集合不大,為什麼?因為寫的時候會複製新集合
- 實時性要求不高,為什麼,因為有可能會讀取到舊的集合資料
參考文章:如何執行緒安全地遍歷List:Vector、CopyOnWriteArrayList
小編大四,正在實習,學識尚淺,歡迎評論交流,一起學習,一起進步!