CopyOnWrite
- CopyOnWrite是什么哈垢?
- CopyOnWriteArrayList源碼分享?
- CopyOnWriteArrayList使用場景惫霸?
- CopyOnWriteArrayList有什么優(yōu)缺點猫缭?
如果你是求職者,你想想看怎么回答上面的問題它褪?
緣由
前段時間面試好多個人饵骨,問是否用過CopyOnWriteList
,發(fā)現(xiàn)好多人都沒有用過翘悉,感覺挺驚訝的茫打。
CopyOnWrite
看字面意思大概就可以明白了,copy集合之后再進(jìn)行write操作,我們也稱這個為寫時復(fù)制容器。
這個從 JDK 1.5版本就已經(jīng)有了老赤,Java并發(fā)包中有兩個實現(xiàn)這個機(jī)制的容器轮洋,分別是
CopyOnWriteArrayList
和CopyOnWriteArraySet
。
CopyOnWrite這個容器非常有用抬旺,特別是在并發(fā)的時候能夠提升效率弊予,很多并發(fā)的的場景中都可以用到CopyOnWrite
的容器,我們在生產(chǎn)環(huán)境也用到過开财,今天托尼就和大家順便講講這個容器汉柒。
CopyOnWrite是什么
官方解釋
CopyOnWriteArrayList 是ArrayList的線程安全方式的一種方式。它的add责鳍、set方法底層實現(xiàn)都是重新復(fù)制一個新的容器來操作的碾褂。
CopyOnWriteArrayList 與ArrayList不同之處在于添加元素的時候會加上鎖。
CopyOnWriteArrayList在修改容器元素的時候并不是直接在原來的數(shù)組上進(jìn)行修改历葛,它是先拷貝一份正塌,然后在拷貝的數(shù)組上進(jìn)行修改,在修改完成之后將引用賦給原來的數(shù)組恤溶。
CopyOnWriteArrayList源碼分享
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
- 實現(xiàn)了List接口乓诽,List的一種實現(xiàn)方式
- 實現(xiàn)RandomAccess接口,看名稱就知道隨機(jī)訪問咒程,和數(shù)組訪問一樣根據(jù)下標(biāo)
- 實現(xiàn)Cloneable接口鸠天,代表可以克隆
- 實現(xiàn)了Serializable接口接口,代表可以被序列化
當(dāng)容器被初始化添加元素成功之后帐姻,多個線程讀取容器中的元素粮宛,如果此刻沒有元素的添加,并發(fā)多個線程讀取出來的數(shù)據(jù)大家都是一樣的卖宠,可以理解為線程安全的 巍杈。
如果此刻有個線程往容器中添加一個新的元素,這個時候CopyOnWriteArrayList就會拷貝一個新的數(shù)組出來扛伍,將新的元素添加到新的數(shù)組中筷畦。
在添加元素的這段時間里,如果多線程訪問容器中的元素刺洒,將會讀取到舊的數(shù)據(jù)鳖宾,等添加元素成功之后會將新的引用地址賦值給舊的list引用地址。
代碼分享:
- add 方法
public boolean add(E e) {
//加鎖操作
final ReentrantLock lock = this.lock;
lock.lock();
try {
//獲取原數(shù)組
Object[] elements = getArray();
int len = elements.length;
// 復(fù)制一個新數(shù)組
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 將新的元素添加到新數(shù)組里面
newElements[len] = e;
// 將原數(shù)組的引用指向新數(shù)組
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
大家要注意上面的代碼中ReentrantLock
逆航,在添加新元素的時候有加鎖操作鼎文,多線程的情況下防止產(chǎn)生臟數(shù)據(jù)。
- get方法
public E get(int index) {
return get(getArray(), index);
}
讀的時候不會加鎖因俐,寫的時候會加上鎖拇惋,這個時候如果多線程正好寫數(shù)據(jù)周偎,讀取的時候還是會讀取到舊的數(shù)據(jù)。
- set方法
public E set(int index, E element) {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//獲取原來數(shù)組
Object[] elements = getArray();
// 通過索引獲取原來的地址
E oldValue = get(elements, index);
// 判斷新舊兩個值是否相等
if (oldValue != element) {
int len = elements.length;
// 拷貝新的數(shù)組
Object[] newElements = Arrays.copyOf(elements, len);
//根據(jù)索引修改元素
newElements[index] = element;
// 將原數(shù)組的引用指向新數(shù)組
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
//為了確保 voliatile 的語義撑帖,所以盡管寫操作沒有改變數(shù)據(jù)蓉坎,還是調(diào)用set方法
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
- 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;
if (numMoved == 0)
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();
}
}
同樣也很簡單,都是使用 System.arraycopy胡嘿、Arrays.copyOf移動元素進(jìn)行元素的刪除操作蛉艾。
- CopyOnWriteArrayList迭代
針對iterator使用了一個叫COWIterator的迭代器,專門針對CopyOnWrite
的迭代器衷敌,因為不支持寫操作勿侯,如上面add、set缴罗、remove都會拋出異常罐监,都是不支持的。
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator.
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator.
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
舉個例子
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String next = iterator.next();
// 這句會報錯的??
iterator.remove();
}
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178)
也正好驗證了迭代的時候UnsupportedOperationException
異常瞒爬。
CopyOnWriteArrayList使用場景
從上面的代碼我們可以看出來了弓柱,適用于多讀少寫的場景,比如電商的商品列表侧但,添加新商品和讀取商品就可以用矢空,其他場景小伙伴們可以想想看。
CopyOnWriteArrayList有什么優(yōu)缺點
缺點:
1禀横、內(nèi)存占用屁药,因為寫時復(fù)制的原理,所以在添加新元素的時候會復(fù)制一份柏锄,此刻內(nèi)存中就會有兩份對象酿箭,比如這個時候有200M,你在復(fù)制一份400M趾娃,那么此刻會產(chǎn)生頻繁的JVM的Yong GC和Full GC缭嫡,
嚴(yán)重的會進(jìn)行STW
Java中Stop-The-World機(jī)制簡稱STW,是在執(zhí)行垃圾收集算法時,Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。
2抬闷、數(shù)據(jù)一致性問題妇蛀,因為CopyOnWrite容器只能保證最終的數(shù)據(jù)一致性,并不能保證數(shù)據(jù)的實時性笤成,也就是不具備原子性的效果评架。
3、數(shù)據(jù)修改炕泳,隨著數(shù)組的元素越來越多纵诞,修改的時候拷貝數(shù)組將會越來越耗時。
優(yōu)點:
1培遵、多讀少寫浙芙,很多時候我們的系統(tǒng)應(yīng)對的都是讀多寫少的并發(fā)場景登刺,讀操作是無鎖操作所以性能較高。
最后說說
- Vector
- ArrayList
- CopyOnWriteArrayList
這三個集合類都繼承List接口
1茁裙、ArrayList是線程不安全的
2塘砸、Vector是比較古老的線程安全的节仿,但性能不行
3晤锥、CopyOnWriteArrayList在兼顧了線程安全的同時,又提高了并發(fā)性廊宪,性能比Vector要高