Copy-On-Write
是什麼?首先我講一下什麼是Copy-On-Write
,顧名思義,在計算機中就是當你想要對一塊內存進行修改時,咱們不在原有內存塊中進行寫
操做,而是將內存拷貝一份,在新的內存中進行寫
操做,寫
完以後呢,就將指向原來內存指針指向新的內存,原來的內存就能夠被回收掉嘛!html
網上兄弟們說了,這是一種用於程序設計中的優化策略
,是一種延時懶惰策略
。都說優化優化,那麼到底優化了哪些問題呢?java
先給你們一份代碼:數組
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()));
}
}
複製代碼
迭代
表示我當前正在讀取某種集合
中的數據,屬於讀
操做;這裏暴露的問題是什麼呢?bash
解決:多線程
CopyOnWriteArrayList
避免了多線程操做List線程不安全的問題CopyOnWriteArrayList
介紹從JDK1.5開始Java併發包裏提供了兩個使用CopyOnWrite
機制實現的併發容器,它們是CopyOnWriteArrayList
和CopyOnWriteArraySet
。CopyOnWrite
容器很是有用,能夠在很是多的併發場景中使用到。併發
CopyOnWriteArrayList
原理:app
上面已經講了,就是在寫的時候不對原集合進行修改,而是從新複製一份,修改完以後,再移動指針
複製代碼
那麼你可能會問?就算是對原集合進行復制,在多線程環境中不也是同樣會致使寫入衝突嗎?沒錯,可是你可能還不知道CopyOnWriteArrayList
中增長刪除元素的實現細節,下面我就說說網上總是提到的add()方法
ide
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()));
}
}
複製代碼
CopyOnWriteArrayList
優缺點缺點:
優勢:
像ArrayList
、Vector
這種集合多線程遍歷迭代問題,記住,Vector
雖然線程安全,只不過是加了synchronized
關鍵字,迭代問題徹底沒有解決!CopyOnWriteArrayList
使用場景參考文章:如何線程安全地遍歷List:Vector、CopyOnWriteArrayList
小編大四,正在實習,學識尚淺,歡迎評論交流,一塊兒學習,一塊兒進步!