天天看點

Java集合類源碼分析(四):List接口之Vector實作類

系列文章目錄

Java集合類源碼分析(一):Java集合類總覽

Java集合類源碼分析(二):List接口之ArrayList實作類

Java集合類源碼分析(三):List接口之ArrayList實作類

Java集合類源碼分析(四):List接口之Vector實作類

Java集合類源碼分析(五):Map接口之HashMap實作類

Java集合類源碼分析(六):Map接口之HashTable實作類

Java集合類源碼分析(七):Map接口之LinkedHashMap實作類

Java集合類源碼分析(八):Map接口之TreeMap實作類

Vector實作了List接口,與ArrayList一樣可以維護一個插入順序,但ArrayList比Vector快,它是非同步的,若涉及到多線程,用Vector會比較好一些,在非多線程環境中,Vector對于元素的查詢、添加、删除和更新操作效果不是很好。

        Vector 可實作自動增長的對象數組。 java.util.vector提供了向量類(vector)以實作類似動态數組的功能。建立了一個向量類的對象後,可以往其中随意插入不同類的對象,即不需顧及類型也不需預先標明向量的容量,并可以友善地進行查找。對于預先不知或者不願預先定義數組大小,并且需要頻繁地進行查找,插入,删除工作的情況。可以考慮使用向量類。其繼承結構圖如下:

Java集合類源碼分析(四):List接口之Vector實作類

1.Vector的使用

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

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

        方法一是系統自動對向量進行管理,建立初始容量為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); 
           

        (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 ,在添加一個元素的時候,它可能會有兩步來完成:

  1. 在 Items[Size] 的位置存放此元素;
  2. 增大 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。
           

參考:

1. Java中vector的使用詳解

2. Vector 是線程安全的?

3.ArrayList/Vector的原理、線程安全和疊代Fail-Fast