請戳GitHub原文: github.com/wangzhiwubi…java
請戳GitHub原文: github.com/wangzhiwubi…git
Java高級特性加強-集合github
本部分網絡上有大量的資源能夠參考,在這裏作了部分整理並作了大量勘誤,感謝前輩的付出,每節文章末尾有引用列表~ ####多線程 ###集合框架 ###NIO ###Java併發容器
本章是"併發容器"的CopyOnWriteArrayList篇。接下來,會先對CopyOnWriteArrayList進行基本介紹,而後再說明它的原理,接着經過代碼去分析,最後經過示例更進一步的瞭解CopyOnWriteArrayList。內容包括:
它至關於線程安全的ArrayList。和ArrayList同樣,它是個可變數組;可是和ArrayList不一樣的時,它具備如下特性:
CopyOnWriteArrayList的數據結構,以下圖所示:
說明: 1.CopyOnWriteArrayList實現了List接口,所以它是一個隊列。 2.CopyOnWriteArrayList包含了成員lock。每個CopyOnWriteArrayList都和一個互斥鎖lock綁定,經過lock,實現了對CopyOnWriteArrayList的互斥訪問。 3. CopyOnWriteArrayList包含了成員array數組,這說明CopyOnWriteArrayList本質上經過數組實現的。 下面從「動態數組」和「線程安全」兩個方面進一步對CopyOnWriteArrayList的原理進行說明。 1. CopyOnWriteArrayList的「動態數組」機制 -- 它內部有個「volatile數組」(array)來保持數據。在「添加/修改/刪除」數據時,都會新建一個數組,並將更新後的數據拷貝到新建的數組中,最後再將該數組賦值給「volatile數組」。這就是它叫作CopyOnWriteArrayList的緣由!CopyOnWriteArrayList就是經過這種方式實現的動態數組;不過正因爲它在「添加/修改/刪除」數據時,都會新建數組,因此涉及到修改數據的操做,CopyOnWriteArrayList效率很低;可是單單只是進行遍歷查找的話,效率比較高。 2. CopyOnWriteArrayList的「線程安全」機制 -- 是經過volatile和互斥鎖來實現的。(01) CopyOnWriteArrayList是經過"volatile數組"來保存數據的。一個線程讀取volatile數組時,總能看到其它線程對該volatile變量最後的寫入;就這樣,經過volatile提供了"讀取到的數據老是最新的"這個機制的保證。(02) CopyOnWriteArrayList經過互斥鎖來保護數據。在"添加/修改/刪除"數據時,會先"獲取互斥鎖",再修改完畢以後,先將數據更新到「volatile數組」中,而後再"釋放互斥鎖",這樣,就達到了保護數據的目的。// 建立一個空列表。
CopyOnWriteArrayList()
// 建立一個按 collection 的迭代器返回元素的順序包含指定 collection 元素的列表。
CopyOnWriteArrayList(Collection<? extends E> c)
// CopyOnWriteArrayList(E[] toCopyIn)
建立一個保存給定數組的副本的列表。
// 將指定元素添加到此列表的尾部。
boolean add(E e)
// 在此列表的指定位置上插入指定元素。
void add(int index, E element)
// 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中的全部元素添加此列表的尾部。
boolean addAll(Collection<? extends E> c)
// 從指定位置開始,將指定 collection 的全部元素插入此列表。
boolean addAll(int index, Collection<? extends E> c)
// 按照指定 collection 的迭代器返回元素的順序,將指定 collection 中還沒有包含在此列表中的全部元素添加列表的尾部。
int addAllAbsent(Collection<? extends E> c)
// 添加元素(若是不存在)。
boolean addIfAbsent(E e)
// 今後列表移除全部元素。
void clear()
// 返回此列表的淺表副本。
Object clone()
// 若是此列表包含指定的元素,則返回 true。
boolean contains(Object o)
// 若是此列表包含指定 collection 的全部元素,則返回 true。
boolean containsAll(Collection<?> c)
// 比較指定對象與此列表的相等性。
boolean equals(Object o)
// 返回列表中指定位置的元素。
E get(int index)
// 返回此列表的哈希碼值。
int hashCode()
// 返回第一次出現的指定元素在此列表中的索引,從 index 開始向前搜索,若是沒有找到該元素,則返回 -1。
int indexOf(E e, int index)
// 返回此列表中第一次出現的指定元素的索引;若是此列表不包含該元素,則返回 -1。
int indexOf(Object o)
// 若是此列表不包含任何元素,則返回 true。
boolean isEmpty()
// 返回以恰當順序在此列表元素上進行迭代的迭代器。
Iterator<E> iterator()
// 返回最後一次出現的指定元素在此列表中的索引,從 index 開始向後搜索,若是沒有找到該元素,則返回 -1。
int lastIndexOf(E e, int index)
// 返回此列表中最後出現的指定元素的索引;若是列表不包含此元素,則返回 -1。
int lastIndexOf(Object o)
// 返回此列表元素的列表迭代器(按適當順序)。
ListIterator<E> listIterator()
// 返回列表中元素的列表迭代器(按適當順序),從列表的指定位置開始。
ListIterator<E> listIterator(int index)
// 移除此列表指定位置上的元素。
E remove(int index)
// 今後列表移除第一次出現的指定元素(若是存在)。
boolean remove(Object o)
// 今後列表移除全部包含在指定 collection 中的元素。
boolean removeAll(Collection<?> c)
// 只保留此列表中包含在指定 collection 中的元素。
boolean retainAll(Collection<?> c)
// 用指定的元素替代此列表指定位置上的元素。
E set(int index, E element)
// 返回此列表中的元素數。
int size()
// 返回此列表中 fromIndex(包括)和 toIndex(不包括)之間部分的視圖。
List<E> subList(int fromIndex, int toIndex)
// 返回一個按恰當順序(從第一個元素到最後一個元素)包含此列表中全部元素的數組。
Object[] toArray()
// 返回以恰當順序(從第一個元素到最後一個元素)包含列表全部元素的數組;返回數組的運行時類型是指定數組的運行時類型。
<T> T[] toArray(T[] a)
// 返回此列表的字符串表示形式。
String toString()
複製代碼
下面咱們從"建立,添加,刪除,獲取,遍歷"這5個方面去分析CopyOnWriteArrayList的原理。 1. 建立 CopyOnWriteArrayList共3個構造函數。它們的源碼以下:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
複製代碼
說明:這3個構造函數都調用了setArray(),setArray()的源碼以下:
private volatile transient Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
複製代碼
說明:setArray()的做用是給array賦值;其中,array是volatile transient Object[]類型,即array是「volatile數組」。關於volatile關鍵字,咱們知道「volatile能讓變量變得可見」,即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入。正在因爲這種特性,每次更新了「volatile數組」以後,其它線程都能看到對它所作的更新。關於transient關鍵字,它是在序列化中才起做用,transient變量不會被自動序列化。 2. 添加 以add(E e)爲例,來對"CopyOnWriteArrayList"的添加操做進行說明。下面是add(E e)的代碼:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 獲取「鎖」
lock.lock();
try {
// 獲取原始」volatile數組「中的數據和數據長度。
Object[] elements = getArray();
int len = elements.length;
// 新建一個數組newElements,並將原始數據拷貝到newElements中;
// newElements數組的長度=「原始數組的長度」+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 將「新增長的元素」保存到newElements中。
newElements[len] = e;
// 將newElements賦值給」volatile數組「。
setArray(newElements);
return true;
} finally {
// 釋放「鎖」
lock.unlock();
}
}
複製代碼
說明: add(E e)的做用就是將數據e添加到」volatile數組「中。它的實現方式是,新建一個數組,接着將原始的」volatile數組「的數據拷貝到新數組中,而後將新增數據也添加到新數組中:最後,將新數組賦值給」volatile數組「。在add(E e)中有兩點須要關注。 第一,在」添加操做「開始前,獲取獨佔鎖(lock),若此時有須要線程要獲取鎖,則必須等待;在操做完畢後,釋放獨佔鎖(lock),此時其它線程才能獲取鎖。經過獨佔鎖,來防止多線程同時修改數據!lock的定義以下:
transient final ReentrantLock lock =
new ReentrantLock();
複製代碼
第二,操做完畢時,會經過setArray()來更新」volatile數組「。並且,前面咱們提過」即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入「;這樣,每次添加元素以後,其它線程都能看到新添加的元素。 3. 獲取 以get(int index)爲例,來對「CopyOnWriteArrayList的刪除操做」進行說明。下面是get(int index)的代碼:
public E get(int index) { return get(getArray(), index); }private E get(Object[] a, int index) { return (E) a[index]; }
說明:get(int index)的實現很簡單,就是返回"volatile數組"中的第index個元素。 4. 刪除 以remove(int index)爲例,來對「CopyOnWriteArrayList的刪除操做」進行說明。下面是remove(int index)的代碼:public E remove(int index) {
final ReentrantLock lock = this.lock;
// 獲取「鎖」
lock.lock();
try {
// 獲取原始」volatile數組「中的數據和數據長度。
Object[] elements = getArray();
int len = elements.length;
// 獲取elements數組中的第index個數據。
E oldValue = get(elements, index);
int numMoved = len - index - 1;
// 若是被刪除的是最後一個元素,則直接經過Arrays.copyOf()進行處理,而不須要新建數組。
// 不然,新建數組,而後將」volatile數組中被刪除元素以外的其它元素「拷貝到新數組中;最後,將新數組賦值給」volatile數組「。
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();
}
}
複製代碼
**說明:**remove(int index)的做用就是將」volatile數組「中第index個元素刪除。它的實現方式是,若是被刪除的是最後一個元素,則直接經過Arrays.copyOf()進行處理,而不須要新建數組。不然,新建數組,而後將"volatile數組中被刪除元素以外的其它元素"拷貝到新數組中;最後,將新數組賦值給"volatile數組"。和add(E e)同樣,remove(int index)也是」在操做以前,獲取獨佔鎖;操做完成以後,釋放獨佔是「;而且"在操做完成時,會經過將數據更新到volatile數組中"。 5. 遍歷 以iterator()爲例,來對CopyOnWriteArrayList的遍歷操做進行說明。 下面是iterator()的代碼:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
複製代碼
說明:iterator()會返回COWIterator對象。COWIterator實現額ListIterator接口,它的源碼以下:
private static class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
// 獲取下一個元素
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
// 獲取上一個元素
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
}
複製代碼
說明:COWIterator不支持修改元素的操做。例如,對於remove(),set(),add()等操做,COWIterator都會拋出異常!另外,須要提到的一點是,CopyOnWriteArrayList返回迭代器不會拋出ConcurrentModificationException異常,即它不是fail-fast機制的!
下面,咱們經過一個例子去對比ArrayList和CopyOnWriteArrayList。
import java.util.*;
import java.util.concurrent.*;
/*
* CopyOnWriteArrayList是「線程安全」的動態數組,而ArrayList是非線程安全的。
*
* 下面是「多個線程同時操做而且遍歷list」的示例
* (01) 當list是CopyOnWriteArrayList對象時,程序能正常運行。
* (02) 當list是ArrayList對象時,程序會產生ConcurrentModificationException異常。
*
*/
public class CopyOnWriteArrayListTest1 {
// TODO: list是ArrayList對象時,程序會出錯。
//private static List<String> list = new ArrayList<String>();
private static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
// 同時啓動兩個線程對list進行操做!
new MyThread("ta").start();
new MyThread("tb").start();
}
private static void printAll() {
String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
// 「線程名」 + "-" + "序號"
String val = Thread.currentThread().getName()+"-"+i;
list.add(val);
// 經過「Iterator」遍歷List。
printAll();
}
}
}
}
複製代碼
其中一次運行結果:
ta-1, tb-1, ta-1,
tb-1,
ta-1, ta-1, tb-1, tb-1, tb-2,
tb-2, ta-1, ta-2,
tb-1, ta-1, tb-2, tb-1, ta-2, tb-2, tb-3,
ta-2, ta-1, tb-3, tb-1, ta-3,
tb-2, ta-1, ta-2, tb-1, tb-3, tb-2, ta-3, ta-2, tb-4,
tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4,
ta-2, ta-1, tb-3, tb-1, ta-3, tb-2, tb-4, ta-2, ta-4, tb-3, tb-5,
ta-3, ta-1, tb-4, tb-1, ta-4, tb-2, tb-5, ta-2, ta-5,
tb-3, ta-1, ta-3, tb-1, tb-4, tb-2, ta-4, ta-2, tb-5, tb-3, ta-5, ta-3, tb-6,
tb-4, ta-4, tb-5, ta-5, tb-6, ta-6,
複製代碼
結果說明:若是將源碼中的list改爲ArrayList對象時,程序會產生ConcurrentModificationException異常。
GitHub: github.com/wangzhiwubi…關注公衆號,內推,面試,資源下載,關注更多大數據技術~
預計更新500+篇文章,已經更新50+篇~
複製代碼