CopyOnWriteArray和Arraylist和Vector

微信公衆號: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支持讀多寫少的併發情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章