讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議80:多線程使用Vector或HashTable >

建議80:多線程使用Vector或HashTable

Vector是ArrayList的多線程版本,HashTable是HashMap的多線程版本,這些概念我們都很清楚,也被前輩囑咐過很多次,但我們經常會逃避使用Vector和HashTable,因為用得少,不熟嘛!只有在真正需要的時候才會想要使用它們,但問題是什麼時候算真正需要呢?我們來看一個例子,看看使用線程安全的Vector是否可以解決問題,代碼如下:


public static void main(Stringargs){

//火車票列表

final List<String>tickets=new ArrayList<String>();

//初始化票據池

for(int i=0;i<100000;i++){

tickets.add("火車票"+i);

}

//退票

Thread returnThread=new Thread(){

public void run(){

while(true){

tickets.add("車票"+new Random().nextInt());

}

};

};

//售票

Thread saleThread=new Thread(){

public void run(){

for(String ticket:tickets){

tickets.remove(ticket);

}

};

};

//啟動退票線程

returnThread.start();

//啟動售票線程

saleThread.start();

}


模擬火車站售票程序,先初始化一堆火車票,然後開始出售,同時也有退票產生,這段程序有沒有問題?可能會有讀者看出了問題,ArrayList是線程不安全的,兩個線程訪問同一個ArrayList數組肯定會有問題。

沒錯,確定有問題,運行結果如下:


Exception in thread"Thread-1"java.util.ConcurrentModifcationException

at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

at java.util.AbstractList$Itr.next(AbstractList.java:343)

at Client$2.run(Client.java:28)


運氣好的話,該異常馬上就會報出。也許有人會說這是一個典型錯誤,只須把ArrayList替換成Vector即可解決問題,真的是這樣嗎?我們把ArrayList替換成Vector後,結果照舊,仍然拋出相同的異常,Vector已經是線程安全的,為什麼還報這個錯誤呢?

這是因為他混淆了線程安全和同步修改異常,基本上所有的集合類都有一個叫做快速失敗(Fail-Fast)的校驗機制,當一個集合在被多個線程修改並訪問時,就可能會出現ConcurrentModificationException異常,這是為了確保集合方法一致而設置的保護措施,它的實現原理就是我們經常提到的modCount修改計數器:如果在讀列表時,modCount發生變化(也就是有其他線程修改)則會拋出ConcurrentModificationException異常。這與線程同步是兩碼事,線程同步是為了保護集合中的數據不被髒讀、髒寫而設置的,我們來看線程安全到底用在什麼地方,代碼如下:


public static void main(Stringargs){

//火車票列表

final List<String>tickets=new ArrayList<String>();

//初始化票據池

for(int i=0;i<100000;i++){

tickets.add("火車票"+i);

}

//10個窗口售票

for(int i=0;i<10;i++){

new Thread(){

public void run(){

while(true){

System.out.println(Thread.currentThread().getId()

+"——"+tickets.remove(0));

}

};

}.start();

}

}


還是火車站售票程序,有10個窗口在賣火車票,程序打印出窗口號(也就是線程號)和車票編號,很快我們就會看到這樣的輸出:


13——火車票96531

10——火車票96531

9——火車票96530

16——火車票96530


注意看,上面有兩個線程在賣同一張火車票,這才是線程不同步的問題,此時把ArrayList修改為Vector即可解決問題,因為Vector的每個方法前都加上了synchronized關鍵字,同時只會允許一個線程進入該方法,確保了程序的可靠性。

雖然在系統開發中我們一再說明,除非必要,否則不要使用synchronized,這是從性能的角度考慮的,但是一旦涉及多線程時(注意這裡說的是真正的多線程,不是並發修改的問題,比如一個線程增加,一個線程刪除,這不屬於多線程的範疇),Vector會是最佳選擇,當然自己在程序中加synchronized也是可行的方法。

HashMap的線程安全類HashTable與此相同,不再贅述。

注意 多線程環境下考慮使用Vector或HashTable。