微信公衆號:Java成長錄
感興趣可以關注下哦,Java知識點,學習路線規劃,Java相關電子書,一起學習呀!。
//////////////////////////////////////////////////////////////////////////
一,相似點
1,CopyOnWriteArray和Arraylist和Vector都是實現了List<>接口;
2,底層都是由動態數組支持,並都支持 增 刪 改 查操作;
以下是部分源碼
CopyOnWriteArray:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private transient volatile Object[] array;
}
Arraylist:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
transient Object[] elementData; // non-private to simplify nested class access
}
Vector:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
protected Object[] elementData;
}
二,差異處
1,非線程安全:ArrayList
線程安全:Vector,CopyOnWriteArray
線程安全的設計主要體現在以下增刪改查方面,所以下面將從源碼橫向比較下三種集合的不同?
CopyOnWriteArray:
- 增刪改之前先持有一個ReentrantLock lock = new ReentrantLock(),操作完之後釋放;
- 底層是用volatile transient聲明的數組 array,可以實現線程間可見性;
- 讀寫分離,寫時複製出一個新的數組,完成插入、修改或者移除操作後將新數組賦值給array;
a,增加元素:
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);
return true;
} finally {
//釋放重入鎖
lock.unlock();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
b,刪除元素:
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];
//將舊數組index+1開始複製到新數組index開始,複製元素個數numMoved
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
//結束釋放鎖
lock.unlock();
}
}
c,修改元素值
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
//開始獲得鎖
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
//創建新數組
Object[] newElements = Arrays.copyOf(elements, len);
//修改元素值
newElements[index] = element;
//將引用指向新得數組
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
//結束釋放鎖
lock.unlock();
}
}
d,查詢元素
//直接獲取index對應的元素
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
CopyOnWriteArray小結:增刪改的時候獲取鎖,保證併發安全性,array數組被volatile修飾,線程間可見性,讀的時候是沒有鎖的,讀操作過程中如果由增刪改操作,那麼讀操作依然能讀,不過讀到的可能是舊值,但是不會導致獲取不到的情況。
ArrayList:
- 插入前會判斷數組容量是否足夠,不夠的話會進行擴容,就是新建一個新的數組,然後將老的數據裏面的元素複製到新的數組裏面
- 移除元素的時候也涉及到數組中元素的移動,刪除指定index位置的元素,然後將index+1至數組最後一個元素往前移動一個格
- modCount表示增加刪除元素操作的次數,主要目的是保證讀取的時候不允許對數組在進行插入或刪除操作,如果modCount不一致,會報異常ConcurrentModificationException
增加元素:
public boolean add(E e) {
//進行數組容量判斷,不夠就擴容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
//檢查是否會越界
rangeCheckForAdd(index);
//進行數組容量判斷,不夠就擴容
ensureCapacityInternal(size + 1); // Increments modCount!!
//將index至數據最後一個元素整體往後移動一格,然後插入新的元素
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
刪除元素:
public E remove(int index) {
//判斷是否越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
//若該元素不是最後一個元素的話,將index+1至數組最後一個元素整體向前移動一格
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
修改元素:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
獲取元素:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
ArrayList小結:ArrayList底層是數組,支持動態擴容,擴容爲原來的1.5倍,由於數組的結構導致,ArrayList查詢和修改元素速率很快O(1),但是增加,刪除元素O(N)比較慢,需要將元素整體移動去騰出位子,或者去除一個位子。
另外,由於線程不安全問題也是一個問題的源點,ArrayList會越界嗎?
首先答案是會越界,是數組都會有越界的可能性,但是不是進行插入前都會判斷是否越界並且支持動態擴容嗎?
單線程是不會在正常情況下越界的,來看下多線程如何越界的吧。
舉個例子:
集合默認長度10,第一次擴容位10+10/2 =15,當集合中已經添加了14個元素時,一個線程率先進入add()方法,在執行ensureCapacityInternal(size + 1)時,發現還可以添加一個元素,故數組沒有擴容,但隨後該線程被阻塞在此處。接着另一線程進入add()方法,執行ensureCapacityInternal(size + 1),由於前一個線程並沒有添加元素,故size依然爲14,依然不需要擴容,所以該線程就開始添加元素,使得size++,變爲15,數組已經滿了。而剛剛阻塞在elementData[size++] = e;語句之前的線程開始執行,它要在集合中添加第16個元素,而數組容量只有15個,所以就發生了數組下標越界異常!
Vector:
- 可以在申明一個Vector時候,輸入一個capacityIncrement,申明擴容量,不申明默認擴爲兩倍;
- 增加刪除synchronize關鍵字修適,保證併發安全性,但是效率會降低;
增加元素:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public void add(int index, E element) {
insertElementAt(element, index);
}
刪除元素:
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
修改元素:
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
讀取元素:
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
Vector小結:可以看到Vector在增刪改查方法都用到了synchronized關鍵字進行了修飾,所以能保證線程安全,但是也會導致效率低下,此集合也是基本屬於淘汰的集合啦。
總結:ArrayList,不具有線程安全性,如果想要安全的集合,可以使用其他支持線程安全的集合,或者Collections.synchronizedList生成線程安全集合,也是最常用的List集合;Vector是增刪改查方法都加了synchronized關鍵字來保證同步,但是每個方法執行的時候都要去獲得鎖,性能就會大大下降,而CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的性能就好於Vector,CopyOnWriteArrayList支持讀多寫少的併發情況。