Java多線程并發(fā)編程中并發(fā)容器第二篇之List的并發(fā)類講解
概述
本文我們將詳細(xì)講解list對(duì)應(yīng)的并發(fā)容器以及用代碼來測(cè)試ArrayList霉晕、vector以及CopyOnWriteArrayList在100個(gè)線程向list中添加1000個(gè)數(shù)據(jù)后的比較
本文是《凱哥分享Java并發(fā)編程之J.U.C包講解》系列教程中的第六篇。如果想系統(tǒng)學(xué)習(xí),凱哥(kaigejava)建議從第一篇開始看风钻。
從本篇開始,我們就來講解講解Java的并發(fā)容器盗尸。大致思路:先介紹什么是并發(fā)容器麸祷。然后講解list相關(guān)的、map相關(guān)的以及隊(duì)列相關(guān)的杭抠。這個(gè)系列會(huì)有好幾篇文章脸甘。大家最好跟著一篇一篇學(xué)。
正文開始
并發(fā)容器分類講解
CopyOneWriteArrayList
Copy-One-Write:即寫入時(shí)候復(fù)制偏灿。
我們知道在原來List子類中vactor是同步容器線程安全的丹诀。這個(gè)CopyOneWriteArrayList可以理解為是他的并發(fā)替代品。
其底層數(shù)據(jù)結(jié)構(gòu)也是數(shù)值翁垂。和ArrayList的不同之處就在于:在list對(duì)象中新增或者是刪除元素的時(shí)候會(huì)把原來的集合copy一份铆遭,增刪操作是在新的對(duì)象中操作的。操作完成之后沿猜,會(huì)將新的數(shù)組替換原來的數(shù)組枚荣。
我們來看看CopyOnWriteArrayList源碼中的add方法:
編輯
?
我們來看看setArray方法:
編輯
?
發(fā)現(xiàn)了嗎?變量使用的是transient和volatile兩個(gè)關(guān)鍵之來修飾的啼肩。
在之前文章中橄妆,我們知道了volatile關(guān)鍵字是內(nèi)存可見性衙伶。那么transient關(guān)鍵字是干嘛的呢?我們來看下百科解釋:
編輯
?
關(guān)鍵的一句話:用transient關(guān)鍵字修飾的成員變量不用參與序列化過程呼畸。
添加注釋后的源碼:
編輯
?
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//獲取到鎖
lock.lock();
try {
//獲取到原集合
Object[] elements = getArray();
int len = elements.length;
//將原集合copy一份到新的集合中痕支。并設(shè)置新的集合的長(zhǎng)度為原集合長(zhǎng)度+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//將需要新增的元數(shù)添加到新的素組中
newElements[len] = e;
//將新數(shù)組替換原來數(shù)據(jù)。 使用transient和volatitle關(guān)鍵字修飾的
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
代碼很簡(jiǎn)單蛮原,大致流程如下:
先從ReentrantLock中獲取到鎖(這樣在多線程下可以防止其他線程來修改容器list里面內(nèi)容了)卧须;
通過arrays.copyOf方法copy出一份原有數(shù)組長(zhǎng)度+1;
將要添加的元素賦值給copy出來的數(shù)組儒陨;
使用setArray方法將新得數(shù)組替換原有素組花嘶。
因?yàn)槎际荓ist集合。我們就拿ArrayList蹦漠、vector以及CopyOnWriteArrayList進(jìn)行比較:
ArrayList椭员、vector以及CopyOnWriteArrayList比較
業(yè)務(wù)場(chǎng)景描述:
啟動(dòng)100個(gè)線程,向?qū)ο笾刑砑?000個(gè)數(shù)據(jù)笛园。查看各自運(yùn)行結(jié)果耗時(shí)及插入數(shù)據(jù)總數(shù)隘击。代碼在文章最后凱哥會(huì)貼出來。
先用線程非安全的arrayList執(zhí)行效果:
編輯
?
執(zhí)行arryList時(shí)間為 : 112毫秒研铆!
List.size() : 93266
我們發(fā)現(xiàn)list的長(zhǎng)度不對(duì)埋同。正確的應(yīng)該是100*1000.從結(jié)果來看,arrayList丟數(shù)據(jù)了棵红。
使用Vector執(zhí)行后的效果:
編輯
?
執(zhí)行vector時(shí)間為 : 98毫秒凶赁!
List.size() : 100000
執(zhí)行的總數(shù)對(duì),說下同步鎖沒有丟數(shù)據(jù)逆甜。
在來看看copyOnWriteArrayList執(zhí)行效果:
編輯
?
執(zhí)行copyOnWriteArrayList時(shí)間為 : 5951毫秒虱肄!
List.size() : 100000
運(yùn)行后數(shù)據(jù)比較:
運(yùn)行的類運(yùn)行時(shí)長(zhǎng)運(yùn)行結(jié)果
ArrayList112毫秒93266
Vector98毫秒100000
copyOnWriteArrayList5951毫秒100000
從上面表格中我們可以看出非安全線程的容器會(huì)丟數(shù)據(jù)。使用copyOneWriteArrayList耗時(shí)很長(zhǎng)交煞。那是因?yàn)槊看芜\(yùn)行都要copyof一份咏窿。
總結(jié)
copyArrayList(這里凱哥就簡(jiǎn)寫了):是讀寫分離的。在寫的時(shí)候會(huì)復(fù)制一個(gè)新的數(shù)組來完成插入和修改或者刪除操作之后素征,再將新的數(shù)組給array.讀取的時(shí)候直接讀取最新的數(shù)據(jù)集嵌。
因?yàn)樵趯懙臅r(shí)候需要向主內(nèi)存申請(qǐng)控件,導(dǎo)致寫操作的時(shí)候稚茅,效率非常低的(雖然在操作時(shí)候比較慢得纸淮,但是在刪除或者修改數(shù)組的頭和尾的時(shí)候還是很快的平斩。因?yàn)槠鋽?shù)據(jù)結(jié)構(gòu)決定查找頭和尾快亚享,而且執(zhí)行不需要同步鎖)
從上面表中,可以看出copyArrayList雖然保證了線程的安全性绘面,但是寫操作效率太low了欺税。但是相比Vector來說侈沪,在并發(fā)安全方面的性能要比vector好;
CopyArrayList和Vector相比改進(jìn)的地方:
Vector是在新增晚凿、刪除亭罪、修改以及查詢的時(shí)候都使用了Synchronized關(guān)鍵字來保證同步的。但是每個(gè)方法在執(zhí)行的時(shí)候歼秽,都需要獲取到鎖应役,在獲取鎖等待的過程中性能就會(huì)大大的降低的。
CopyArrayList的改進(jìn):只是在新增和刪除的方法上使用了ReentrantLock鎖進(jìn)行(這里凱哥就不截圖源碼了燥筷,自己可以看看源碼)箩祥。在讀的時(shí)候不加鎖的。所以在讀的方面性能要不vector性能要好肆氓。
所以,CopyArrayList支持讀多寫少的并發(fā)情況
CopyOnWriteArrayList的使用場(chǎng)景:
由于讀操作不加鎖袍祖,增刪改三個(gè)操作加鎖的,因此適用于讀多寫少的場(chǎng)景谢揪,
局限性:因?yàn)樽x的時(shí)候不加鎖的蕉陋,讀的效率和普通的arrayList是一樣的。但是請(qǐng)看讀操作:
編輯
?
編輯
?
在get的時(shí)候array使用的是volatile修飾的拨扶。是內(nèi)存可見的凳鬓。所以可以說copyArrayList在讀的時(shí)候不會(huì)出現(xiàn)arrayList讀取到臟數(shù)據(jù)的問題。
Get(i)方法比較如下:
編輯
?
附件:arrayList屈雄、vector村视、copyOnwriteArrayList比較的代碼:
public static void?main(String[] args) {
//使用線程不安全的arrayList
// List arryList = new ArrayList<>();
//使用vector
// List arryList = new Vector<>();
//使用copyOnWriteArrayList
List arryList =?new?CopyOnWriteArrayList<>();
Random random =?new?Random();
Thread [] threadArr =?new?Thread[100];
CountDownLatch latch =?new?CountDownLatch(threadArr.length);
Long beginTime = System.currentTimeMillis();
for(int?i = 0;ilength;i++){
threadArr[i] =?new?Thread(new?Runnable() {
@Override
public void?run() {
for(int?j = 0; j < 1000; j++){
try?{
arryList.add("value"?+ random.nextInt(100000));
}?catch?(Exception e) {
}
}
latch.countDown();
}
});
}
for(Thread t : threadArr){
t.start();
}
try?{
latch.await();
}?catch?(InterruptedException e) {
e.printStackTrace();
}
long?endTime = System.currentTimeMillis();
System.out.println("執(zhí)行copyOnWriteArrayList時(shí)間為 : "?+ (endTime-beginTime) +?"毫秒!");
System.out.println("List.size() : "?+ arryList.size());
}