@[toc]
1. 誕生的曆史和原因
- 代替Vector和SyschronizedList 就像 ConcurrentHashMap 代替了SyschronizedMap的原因一樣
- Vector和SyschronizedList 鎖的粒度太大,并發效率較低,并且疊代時無法編輯
- Copy-On-Write 并發容器還包括CopyOnWriteArraySet,用來代替同步 Set
2. 适用場景
- 讀操作盡可能的快一些,而寫即使慢一些也沒關系
3. 讀寫規則
- 回顧讀寫鎖:讀讀共享、其他都互斥,(寫寫互斥,讀寫互斥,寫讀互斥)
- 讀寫鎖規則的更新:讀取是完全不用加鎖的,并且更厲害的是寫入也不會阻塞讀取操作,隻有寫入寫入才會同步等待
- ArrayList
public static void main(String[] args) {
List list=new ArrayList();
list.add("11");
list.add("22");
list.add("33");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(list);
Object next = iterator.next();
System.out.println(next);
if(next.equals("11")){
list.remove("22");
}
if(next.equals("33")){
list.add("2222");
}
}
}

CopyOnWriteArrayList
public static void main(String[] args) {
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(list);
Object next = iterator.next();
System.out.println(next);
if (next.equals("11")) {
list.remove("22");
}
if (next.equals("33")) {
list.add("2222");
}
}
}
通過案例我們可以發現 ArrayList 疊代修改的時候會 抛出異常 ,而CopyOnWriteArrayList 不會
4. 實作原理&源碼分析
CopyOnWrite的含義
建立新副本、讀寫分離
不可變原理
疊代的時候
我們看下ArrayList 源碼為什麼會報錯
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
點開方法,他會進行一個比較操作是以會出現異常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我們看下 CopyOnWriteArrayList 源碼
這是CopyOnWriteArrayList存放資料的地方,隻能通過getArray擷取
并且他會上鎖,用的ReentrantLock
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
初始化 :構造函數 建立個空的數組
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
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();
//擷取長度 //copy出一個新的數組
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将我們的參數添加到這個位置
newElements[len] = e;
//set方法添加至
setArray(newElements);
return true;
} finally {
//釋放鎖
lock.unlock();
}
}
get方法
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
//判斷 true 和 false
//邏輯簡單了許多
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
5. 缺點
- 資料一緻性問題 :CopyOnWrite隻能保證資料最終一緻性的問題,不能保證資料實時一緻性,是以希望寫入的資料馬上能讀到,不要使用CopyOnWrite
- 記憶體占用問題:因為CopyOnWrite的寫是複制機制,是以在寫的操作,是以記憶體中會存在2個對象
個人部落格位址:
http://blog.yanxiaolong.cn/