java中ArrayList線程不安全和vector線程安全

原文鏈接:https://blog.csdn.net/weixin_43407007/article/details/87901795

版權聲明:本文爲博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/weixin_43407007/article/details/87901795

        Vector實現了List接口,與ArrayList一樣可以維護一個插入順序,但ArrayList比Vector快,它是非同步的,若涉及到多線程,用Vector會比較好一些,在非多線程環境中,Vector對於元素的查詢、添加、刪除和更新操作效果不是很好。
        Vector 可實現自動增長的對象數組。 java.util.vector提供了向量類(vector)以實現類似動態數組的功能。創建了一個向量類的對象後,可以往其中隨意插入不同類的對象,即不需顧及類型也不需預先選定向量的容量,並可以方便地進行查找。對於預先不知或者不願預先定義數組大小,並且需要頻繁地進行查找,插入,刪除工作的情況。可以考慮使用向量類。


1.Vector的使用

        向量類提供類三種構造方法:

public vector()
public vector(int initialcapacity)
public vector(int initialcapacity,int capacityIncrement)

    1
    2
    3

        方法一是系統自動對向量進行管理,創建初始容量爲10的空的Vector;若再增加元素,則成倍的增長;
        方法二根據初始參數initialcapacity創建Vector對象
        方法三中參數capacityincrement給定了每次擴充的擴充值。當capacityincrement爲0的時候,則每次擴充一倍,利用這個功能可以優化存儲。
        在Vector類中主要提供瞭如下方法:
         1)public final synchronized void adddElement(Object obj)
        將obj插入向量的尾部。obj可以是任何類型的對象。對同一個向量對象,亦可以在其中插入不同類的對象。但插入的應是對象而不是數值,所以插入數值時要注意將數組轉換成相應的對象。
        例如:要插入整數1時,不要直接調用v1.addElement(1),正確的方法爲:

    Vector v1 = new Vector();
    Integer integer1 = new Integer(1);
    v1.addElement(integer1);

    1
    2
    3

        (2)public final synchronized void setElementAt(Object obj,int index)
        將index處的對象設置成obj,原來的對象將被覆蓋。
        (3)public final synchronized void insertElement(Object obj,int index)
        在index指定的位置插入obj,原來對象以及此後的對象依次往後順延。
        (4)public final synchronized void removeElement(Object obj)
        從向量中刪除obj,若有多個存在,則從向量頭開始試,刪除找到的第一個與obj相同的向量成員。
        (5)public final synchronized void removeAllElement();
        刪除向量所有的對象
        (6)public fianl synchronized void removeElementAt(int index)
        刪除index所指的地方的對象
        (7)public final int indexOf(Object obj)
        從向量頭開始搜索obj,返回所遇到的第一個obj對應的下標,若不存在此obj,返回-1.
        (8)public final synchronized int indexOf(Object obj,int index)
        從index所表示的下標處開始搜索obj.
        (9)public final int lastindexOf(Object obj)
        從向量尾部開始逆向搜索obj.
        (10)public final synchornized int lastIndex(Object obj,int index)
        從index所表示的下標處由尾至頭逆向搜索obj.
        (11)public final synchornized firstElement()
        獲取向量對象中的首個obj
        (12)public final synchornized Object lastElement()
        獲取向量對象的最後一個obj
        (13)public final int size();
        此方法用於獲取向量元素的個數。它們返回值是向量中實際存在的元素個數,而非向量容量。可以調用方法capacity()來獲取容量值。
        (14)public final synchronized void setsize(int newsize);
        此方法用來定義向量的大小,若向量對象現有成員個數已經超過了newsize的值,則超過部分的多餘元素會丟失。
        (15)public final synchronized Enumeration elements();
        此方法將向量對象對應到一個枚舉類型。java.util包中的其他類中也都有這類方法,以便於用戶獲取對應的枚舉類型。
        (Enumeration類的一個對象Enumeration是java.util中的一個接口類,
在Enumeration中封裝了有關枚舉數據集合的方法。
在Enumeration提供了方法hasMoreElement()來判斷集合中是否還有其他元素和方法nextElement()來判斷集合中是否還有其他元素和方法nextElement()來獲取下一個元素。利用這兩個方法,可以依次獲得集合中的元素。)


2.Vector線程安全與ArrayList的非線程安全


2.1 線程安全與非線程安全

        線程安全就是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程纔可使用。不會出現數據不一致或者數據污染。線程不安全就是不提供數據訪問保護,有可能出現多個線程先後更改數據造成所得到的數據是髒數據。
2.2 Vector內部如何實現線程安全

public class Vector
{
     Object[] elementData;       // 存放元素的數組
     int elementCount;           // 存放元素的實際數量,默認的容量(capacity)是10
     int capacityIncrement;      // 當容量佔滿時,擴容量,如果未指定,則原先的2倍(doubled)
     // 構造函數
     public Vector(int initialCapacity/* 初始容量 */,int capacityIncrement/*擴容量*/){}
}

Vector類中的capacity()/size()/isEmpty()/indexOf()/lastIndexOf()/removeElement()/addElement() 等方法均是 sychronized 的,所以,對Vector的操作均是線程安全的。
       對於Vector的操作均是線程安全這句話還需要注意一點是:如果是單個方法進行調用是線程安全的,但是如果是組合的方式進行調用則需要再次進行同步處理,例如:

if (!vector.contains(element))
    vector.add(element);
    ...
}

       這是經典的 put-if-absent 情況,儘管 contains, add 方法都正確地同步了,但作爲 vector 之外的使用環境,仍然存在 race condition: 因爲雖然條件判斷 if (!vector.contains(element))與方法調用 vector.add(element); 都是原子性的操作 (atomic),但在 if 條件判斷爲真後,那個用來訪問vector.contains 方法的鎖已經釋放,在即將的 vector.add 方法調用之間有間隙,在多線程環境中,完全有可能被其他線程獲得 vector的 lock 並改變其狀態, 此時當前線程的vector.add(element); 正在等待(只不過我們不知道而已)。只有當其他線程釋放了 vector 的 lock 後,vector.add(element); 繼續,但此時它已經基於一個錯誤的假設了。
       單個的方法 synchronized 了並不代表組合(compound)的方法調用具有原子性,使 compound actions 成爲線程安全的可能解決辦法之一還是離不開intrinsic lock (這個鎖應該是 vector 的,但由 client 維護):

// Vector v = ...
    public  boolean putIfAbsent(E x) {
synchronized(v) {
            boolean absent = !contains(x);
            if (absent) {
                add(x);
}
}
        return absent;
    }

 

       所以在回答Vector與ArrayList的區別時,應該這樣回答:
Vector 和 ArrayList 實現了同一接口 List, 但所有的 Vector 的方法都具有 synchronized 關鍵修飾。但對於複合操作,Vector 仍然需要進行同步處理。


2.2 爲什麼ArrayList線程不安全?

舉個栗子:
       一個 ArrayList ,在添加一個元素的時候,它可能會有兩步來完成:

    在 Items[Size] 的位置存放此元素;
    增大 Size 的值。 增大 Size 的值。

       在單線程運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1; 而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也向此 ArrayList 添加元素,因爲此時 Size 仍然等於 0 (注意哦,我們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然後線程A和線程B都繼續運行,都增加 Size 的值。現在看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是“線程不安全”了。
雖然ArrayList是非線程安全的,要想實現線程安全的ArrayList,可在ArrayList的基礎上通過同步塊來實現,或者使用同步包裝器(Collections.synchronizedList),還可以使用J.U.C中的CopyOnWriteArrayList。
       ArrayList的非線程安全帶來的問題:

final ArrayList<String> list = new ArrayList<String>(); // 多線程共享的ArrayList
for(int i=0;i<100;i++) // 多個線程同時進行寫操作
{
     new Thread(new Runnable(){
        @Override
        public void run() {
        for(int j=0;j<1000;j++)
        {
             list.add("hello"); // 多線程下,此處引發ArrayIndexOutOfBoundsException
       }
    }}).start();
}

 

ArrayList的內部原理:

public class ArrayList<E>
{
    private Object[] elementData;      // 存儲元素的數組。其分配的空間長度是capacity。
    private int size;                  // elementData存儲了多少個元素。
    public ArrayList(){this(10);};     // 默認capacity是10
    boolean add(E e)
    {
         ensureCapacityInternal(size + 1);  // capacity至少爲 size+1
         elementsData[size++]=e;            // size++
         return true;
    }
    void ensureCapacityInternal(int minCapacity){
         if(minCapacity > elementData.length)     // 擴容
              grow(minCapacity);
    }
    void grow(int minCapacity){
         int oldCapacity = elementData.length;
         int newCapacity = oldCapacity + (oldCapacity >> 1);     // 約是原先的1.5倍。
         elementData = Arrays.copyOf(elementData,newCapacity );
    }
}

   

如何實現線程安全的ArrayList

    #1:自己手動同步
    public static List<E> list = ... ;
    lock.lock();
    list.add();
    lock.unlock();
    #2:使用同步包裝器
    List<E> syncList = Collections.synchronizedList(new ArrayList<E>());
    迭代時,需要包含在同步塊當中
    synchronized(syncList){
        while(Iterator<E> iter = syncList.iterator();iter.hasNext();){}
    }
    #3:使用J.U.C中的CopyOnWriteArrayList。

 ————————————————
版權聲明:本文爲CSDN博主「MyKelly_2013」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_43407007/article/details/87901795

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