1、概念介紹
你所需要的網(wǎng)站建設(shè)服務(wù),我們均能行業(yè)靠前的水平為你提供.標(biāo)準(zhǔn)是產(chǎn)品質(zhì)量的保證,主要從事做網(wǎng)站、網(wǎng)站建設(shè)、企業(yè)網(wǎng)站建設(shè)、手機(jī)網(wǎng)站開發(fā)、網(wǎng)頁設(shè)計(jì)、成都品牌網(wǎng)站建設(shè)、網(wǎng)頁制作、做網(wǎng)站、建網(wǎng)站。成都創(chuàng)新互聯(lián)公司擁有實(shí)力堅(jiān)強(qiáng)的技術(shù)研發(fā)團(tuán)隊(duì)及素養(yǎng)的視覺設(shè)計(jì)專才。線程安全就是多線程訪問時(shí),采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí),進(jìn)行保護(hù),其他線程不能進(jìn)行訪問直到該線程讀取完,其他線程才可使用。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。
線程不安全就是不提供數(shù)據(jù)訪問保護(hù),多線程先后更改數(shù)據(jù)會(huì)產(chǎn)生數(shù)據(jù)不一致或者數(shù)據(jù)污染的情況。
一般使用synchronized關(guān)鍵字加鎖同步控制,來解決線程不安全問題。
2、線程安全的集合對(duì)象
ArrayList線程不安全,Vector線程安全;
HashMap線程不安全,HashTable線程安全;
StringBuilder線程不安全,StringBuffer線程安全。
3、代碼測(cè)試
ArrayList線程不安全:
在主線程中新建100個(gè)子線程,分別向ArrayList中添加100個(gè)元素,最后打印ArrayList的size。
public?class?Test?{??public?static?void?main(String?[]?args){??????//?用來測(cè)試的List?? ??????List?data?=?new?ArrayList<>();??????//?用來讓主線程等待100個(gè)子線程執(zhí)行完畢?? ??????CountDownLatch?countDownLatch?=?new?CountDownLatch(100);??????//?啟動(dòng)100個(gè)子線程?? ??????for(int?i=0;i<100;i++){ ??????????SampleTask?task?=?new?SampleTask(data,countDownLatch); ??????????Thread?thread?=?new?Thread(task); ??????????thread.start(); ??????}??????try{??????????//?主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行?? ??????????countDownLatch.await(); ??????}catch?(InterruptedException?e){?? ??????????e.printStackTrace();?? ??????}? ??????//?List的size?? ??????System.out.println(data.size()); ??} }class?SampleTask?implements?Runnable?{ ????CountDownLatch?countDownLatch; ????List ?data;????public?SampleTask(List ?data,CountDownLatch?countDownLatch){????????this.data?=?data;????????this.countDownLatch?=?countDownLatch; ????}????@Override ????public?void?run()?{????????//?每個(gè)線程向List中添加100個(gè)元素?? ????????for(int?i?=?0;?i?100;?i++)?? ????????{?? ????????????data.add("1"); ????????}?? ????????//?完成一個(gè)子線程?? ????????countDownLatch.countDown(); ????} }
7次測(cè)試輸出():
99981000010000ArrayIndexOutOfBoundsException1000099679936
Vector線程安全:
在主線程中新建100個(gè)子線程,分別向Vector中添加100個(gè)元素,最后打印Vector的size。
public?class?Test?{??public?static?void?main(String?[]?args){??????//?用來測(cè)試的List?? ??????List?data?=?new?Vector<>();??????//?用來讓主線程等待100個(gè)子線程執(zhí)行完畢?? ??????CountDownLatch?countDownLatch?=?new?CountDownLatch(100);??????//?啟動(dòng)100個(gè)子線程?? ??????for(int?i=0;i<100;i++){ ??????????SampleTask?task?=?new?SampleTask(data,countDownLatch); ??????????Thread?thread?=?new?Thread(task); ??????????thread.start(); ??????}??????try{??????????//?主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行?? ??????????countDownLatch.await(); ??????}catch?(InterruptedException?e){?? ??????????e.printStackTrace();?? ??????}? ??????//?List的size?? ??????System.out.println(data.size()); ??} }class?SampleTask?implements?Runnable?{ ????CountDownLatch?countDownLatch; ????List ?data;????public?SampleTask(List ?data,CountDownLatch?countDownLatch){????????this.data?=?data;????????this.countDownLatch?=?countDownLatch; ????}????@Override ????public?void?run()?{????????//?每個(gè)線程向List中添加100個(gè)元素?? ????????for(int?i?=?0;?i?100;?i++)?? ????????{?? ????????????data.add("1"); ????????}?? ????????//?完成一個(gè)子線程?? ????????countDownLatch.countDown(); ????} }
7次測(cè)試輸出():
10000100001000010000100001000010000
使用synchronized關(guān)鍵字來同步ArrayList:
public?class?Test?{??public?static?void?main(String?[]?args){??????//?用來測(cè)試的List?? ??????List?data?=?new?ArrayList<>();??????//?用來讓主線程等待100個(gè)子線程執(zhí)行完畢?? ??????CountDownLatch?countDownLatch?=?new?CountDownLatch(100);??????//?啟動(dòng)100個(gè)子線程?? ??????for(int?i=0;i<100;i++){ ??????????SampleTask?task?=?new?SampleTask(data,countDownLatch); ??????????Thread?thread?=?new?Thread(task); ??????????thread.start(); ??????}??????try{??????????//?主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行?? ??????????countDownLatch.await(); ??????}catch?(InterruptedException?e){?? ??????????e.printStackTrace();?? ??????}? ??????//?List的size?? ??????System.out.println(data.size()); ??} }class?SampleTask?implements?Runnable?{ ????CountDownLatch?countDownLatch; ????List ?data;????public?SampleTask(List ?data,CountDownLatch?countDownLatch){????????this.data?=?data;????????this.countDownLatch?=?countDownLatch; ????}????@Override ????public?void?run()?{????????//?每個(gè)線程向List中添加100個(gè)元素?? ????????for(int?i?=?0;?i?100;?i++)?? ????????{?? ????????????synchronized(data){ ????????????????data.add("1"); ????????????} ????????}?? ????????//?完成一個(gè)子線程?? ????????countDownLatch.countDown(); ????} }
7次測(cè)試輸出():
10000100001000010000100001000010000
3、原因分析
ArrayList在添加一個(gè)元素的時(shí)候,它會(huì)有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在單線程運(yùn)行的情況下,如果 Size = 0,添加一個(gè)元素后,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個(gè)線程,線程 A 先將元素1存放在位置 0。但是此時(shí) CPU 調(diào)度線程A暫停,線程 B 得到運(yùn)行的機(jī)會(huì)。線程B向此 ArrayList 添加元素2,因?yàn)榇藭r(shí) Size 仍然等于 0 (注意,我們假設(shè)的是添加一個(gè)元素是要兩個(gè)步驟,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然后線程A和線程B都繼續(xù)運(yùn)行,都增加 Size 的值,結(jié)果Size都等于1。
最后,ArrayList中期望的元素應(yīng)該有2個(gè),而實(shí)際元素是在0位置,造成丟失元素,故Size 等于1。導(dǎo)致“線程不安全”。
ArrayList源碼:
@Override?public?boolean?add(E?object)?{ ????????Object[]?a?=?array;????????int?s?=?size;????????if?(s?==?a.length)?{ ????????????Object[]?newArray?=?new?Object[s?+ ????????????????????(s?(MIN_CAPACITY_INCREMENT?/?2)?? ?????????????????????MIN_CAPACITY_INCREMENT?:?s?>>?1)]; ????????????System.arraycopy(a,?0,?newArray,?0,?s); ????????????array?=?a?=?newArray; ????????} ????????a[s]?=?object; ????????size?=?s?+?1; ????????modCount++;????????return?true; ????}
Vector的所有操作方法都被同步了,既然被同步了,多個(gè)線程就不可能同時(shí)訪問vector中的數(shù)據(jù),只能一個(gè)一個(gè)地訪問,所以不會(huì)出現(xiàn)數(shù)據(jù)混亂的情況,所以是線程安全的。
Vector源碼:
@Override ????public?synchronized?boolean?add(E?object)?{????????if?(elementCount?==?elementData.length)?{ ????????????growByOne(); ????????} ????????elementData[elementCount++]?=?object; ????????modCount++;????????return?true; ????}
4、線程安全的集合并不安全
分析以下場(chǎng)景:
synchronized(map){ Object?value?=?map.get(key);if(value?==?null) { ????value?=?new?Object(); ????map.put(key,value); }return?value;}
由于線程安全的集合對(duì)象是基于單個(gè)方法的同步,所以即使map是線程安全的,也會(huì)產(chǎn)生不同步現(xiàn)象。
在非單個(gè)方法的場(chǎng)景下,我們?nèi)匀恍枰褂胹ynchronized加鎖才能保證對(duì)象的同步。
代碼測(cè)試:
public?class?Test?{??public?static?void?main(String?[]?args){??????//?用來測(cè)試的List?? ??????List?data?=?new?Vector<>();??????//?用來讓主線程等待100個(gè)子線程執(zhí)行完畢?? ??????CountDownLatch?countDownLatch?=?new?CountDownLatch(100);??????//?啟動(dòng)100個(gè)子線程?? ??????for(int?i=0;i<1000;i++){ ??????????SampleTask?task?=?new?SampleTask(data,countDownLatch); ??????????Thread?thread?=?new?Thread(task); ??????????thread.start(); ??????}??????try{??????????//?主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行?? ??????????countDownLatch.await(); ??????}catch?(InterruptedException?e){?? ??????????e.printStackTrace();?? ??????}? ??????//?List的size?? ??????System.out.println(data.size()); ??} }class?SampleTask?implements?Runnable?{ ????CountDownLatch?countDownLatch; ????List ?data;????public?SampleTask(List ?data,CountDownLatch?countDownLatch){????????this.data?=?data;????????this.countDownLatch?=?countDownLatch; ????}????@Override ????public?void?run()?{????????//?每個(gè)線程向List中添加100個(gè)元素?? ????????int?size?=?data.size(); ????????data.add(size,"1");? ????????//?完成一個(gè)子線程?? ????????countDownLatch.countDown(); ????} }
997 993 995 996 997 998 997
5、總結(jié)
如何取舍
線程安全必須要使用synchronized關(guān)鍵字來同步控制,所以會(huì)導(dǎo)致性能的降低。
當(dāng)不需要線程安全時(shí),可以選擇ArrayList,避免方法同步產(chǎn)生的開銷;
當(dāng)多個(gè)線程操作同一個(gè)對(duì)象時(shí),可以選擇線程安全的Vector;
線程不安全!=不安全
有人在使用過程中有一個(gè)不正確的觀點(diǎn):我的程序是多線程的,不能使用ArrayList要使用Vector,這樣才安全。
線程不安全并不是多線程環(huán)境下就不能使用。
注意線程不安全條件:多線程操作同一個(gè)對(duì)象。比如上述代碼就是在多個(gè)線程操作同一個(gè)ArrayList對(duì)象。
如果每個(gè)線程中new一個(gè)ArrayList,而這個(gè)ArrayList只在這一個(gè)線程中使用,那么是沒問題的。
線程‘安全’的集合對(duì)象
較復(fù)雜的操作下,線程安全的集合對(duì)象也無法保證數(shù)據(jù)的同步,仍然需要我們來處理。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開啟,新人活動(dòng)云服務(wù)器買多久送多久。