Java并發(fā)編程:同步容器
Java并發(fā)編程:同步容器
為了方便編寫出線程安全的程序,Java里面提供了一些線程安全類和并發(fā)工具,比如:同步容器、并發(fā)容器、阻塞隊(duì)列、Synchronizer(比如CountDownLatch)。今天我們就來討論下同步容器。
以下是本文的目錄大綱:
一.為什么會(huì)出現(xiàn)同步容器?
二.Java中的同步容器類
三.同步容器的缺陷
若有不正之處請(qǐng)多多諒解,并歡迎批評(píng)指正。
請(qǐng)尊重作者勞動(dòng)成果,轉(zhuǎn)載請(qǐng)標(biāo)明原文鏈接:
http://www.rzrgm.cn/dolphin0520/p/3933404.html
一.為什么會(huì)出現(xiàn)同步容器?
在Java的集合容器框架中,主要有四大類別:List、Set、Queue、Map。
List、Set、Queue接口分別繼承了Collection接口,Map本身是一個(gè)接口。
注意Collection和Map是一個(gè)頂層接口,而List、Set、Queue則繼承了Collection接口,分別代表數(shù)組、集合和隊(duì)列這三大類容器。
像ArrayList、LinkedList都是實(shí)現(xiàn)了List接口,HashSet實(shí)現(xiàn)了Set接口,而Deque(雙向隊(duì)列,允許在隊(duì)首、隊(duì)尾進(jìn)行入隊(duì)和出隊(duì)操作)繼承了Queue接口,PriorityQueue實(shí)現(xiàn)了Queue接口。另外LinkedList(實(shí)際上是雙向鏈表)實(shí)現(xiàn)了了Deque接口。
像ArrayList、LinkedList、HashMap這些容器都是非線程安全的。
如果有多個(gè)線程并發(fā)地訪問這些容器時(shí),就會(huì)出現(xiàn)問題。
因此,在編寫程序時(shí),必須要求程序員手動(dòng)地在任何訪問到這些容器的地方進(jìn)行同步處理,這樣導(dǎo)致在使用這些容器的時(shí)候非常地不方便。
所以,Java提供了同步容器供用戶使用。
二.Java中的同步容器類
在Java中,同步容器主要包括2類:
1)Vector、Stack、HashTable
2)Collections類中提供的靜態(tài)工廠方法創(chuàng)建的類
Vector實(shí)現(xiàn)了List接口,Vector實(shí)際上就是一個(gè)數(shù)組,和ArrayList類似,但是Vector中的方法都是synchronized方法,即進(jìn)行了同步措施。
Stack也是一個(gè)同步容器,它的方法也用synchronized進(jìn)行了同步,它實(shí)際上是繼承于Vector類。
HashTable實(shí)現(xiàn)了Map接口,它和HashMap很相似,但是HashTable進(jìn)行了同步處理,而HashMap沒有。
Collections類是一個(gè)工具提供類,注意,它和Collection不同,Collection是一個(gè)頂層的接口。在Collections類中提供了大量的方法,比如對(duì)集合或者容器進(jìn)行排序、查找等操作。最重要的是,在它里面提供了幾個(gè)靜態(tài)工廠方法來創(chuàng)建同步容器類,如下圖所示:

三.同步容器的缺陷
從同步容器的具體實(shí)現(xiàn)源碼可知,同步容器中的方法采用了synchronized進(jìn)行了同步,那么很顯然,這必然會(huì)影響到執(zhí)行性能,另外,同步容器就一定是真正地完全線程安全嗎?不一定,這個(gè)在下面會(huì)講到。
我們首先來看一下傳統(tǒng)的非同步容器和同步容器的性能差異,我們以ArrayList和Vector為例:
1.性能問題
我們先通過一個(gè)例子看一下Vector和ArrayList在插入數(shù)據(jù)時(shí)性能上的差異:
public class Test {
public static void main(String[] args) throws InterruptedException {
ArrayList<Integer> list = new ArrayList<Integer>();
Vector<Integer> vector = new Vector<Integer>();
long start = System.currentTimeMillis();
for(int i=0;i<100000;i++)
list.add(i);
long end = System.currentTimeMillis();
System.out.println("ArrayList進(jìn)行100000次插入操作耗時(shí):"+(end-start)+"ms");
start = System.currentTimeMillis();
for(int i=0;i<100000;i++)
vector.add(i);
end = System.currentTimeMillis();
System.out.println("Vector進(jìn)行100000次插入操作耗時(shí):"+(end-start)+"ms");
}
}
這段代碼在我機(jī)器上跑出來的結(jié)果是:

進(jìn)行同樣多的插入操作,Vector的耗時(shí)是ArrayList的兩倍。
這只是其中的一方面性能問題上的反映。
另外,由于Vector中的add方法和get方法都進(jìn)行了同步,因此,在有多個(gè)線程進(jìn)行訪問時(shí),如果多個(gè)線程都只是進(jìn)行讀取操作,那么每個(gè)時(shí)刻就只能有一個(gè)線程進(jìn)行讀取,其他線程便只能等待,這些線程必須競爭同一把鎖。
因此為了解決同步容器的性能問題,在Java 1.5中提供了并發(fā)容器,位于java.util.concurrent目錄下,并發(fā)容器的相關(guān)知識(shí)將在下一篇文章中講述。
2.同步容器真的是安全的嗎?
也有有人認(rèn)為Vector中的方法都進(jìn)行了同步處理,那么一定就是線程安全的,事實(shí)上這可不一定。看下面這段代碼:
public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for(int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.remove(i);
};
};
Thread thread2 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.get(i);
};
};
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
在我機(jī)器上運(yùn)行的結(jié)果:

正如大家所看到的,這段代碼報(bào)錯(cuò)了:數(shù)組下標(biāo)越界。
也許有朋友會(huì)問:Vector是線程安全的,為什么還會(huì)報(bào)這個(gè)錯(cuò)?很簡單,對(duì)于Vector,雖然能保證每一個(gè)時(shí)刻只能有一個(gè)線程訪問它,但是不排除這種可能:
當(dāng)某個(gè)線程在某個(gè)時(shí)刻執(zhí)行這句時(shí):
for(int i=0;i<vector.size();i++) vector.get(i);
假若此時(shí)vector的size方法返回的是10,i的值為9
然后另外一個(gè)線程執(zhí)行了這句:
for(int i=0;i<vector.size();i++) vector.remove(i);
將下標(biāo)為9的元素刪除了。
那么通過get方法訪問下標(biāo)為9的元素肯定就會(huì)出問題了。
因此為了保證線程安全,必須在方法調(diào)用端做額外的同步措施,如下面所示:
public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for(int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) { //進(jìn)行額外的同步
for(int i=0;i<vector.size();i++)
vector.remove(i);
}
};
};
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for(int i=0;i<vector.size();i++)
vector.get(i);
}
};
};
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
3. ConcurrentModificationException異常
在對(duì)Vector等容器并發(fā)地進(jìn)行迭代修改時(shí),會(huì)報(bào)ConcurrentModificationException異常,關(guān)于這個(gè)異常將會(huì)在后續(xù)文章中講述。
但是在并發(fā)容器中不會(huì)出現(xiàn)這個(gè)問題。
參考資料:
《深入理解Java虛擬機(jī)》
《Java并發(fā)編程實(shí)戰(zhàn)》

浙公網(wǎng)安備 33010602011771號(hào)