您好,登錄后才能下訂單哦!
1.ArrayList源碼和多線程安全問題分析
在分析ArrayList線程安全問題之前,我們線對(duì)此類的源碼進(jìn)行分析,找出可能出現(xiàn)線程安全問題的地方,然后代碼進(jìn)行驗(yàn)證和分析。
1.1 數(shù)據(jù)結(jié)構(gòu)
ArrayList內(nèi)部是使用數(shù)組保存元素的,數(shù)據(jù)定義如下:
transient Object[] elementData; // non-private to simplify nested class access
在ArrayList中此數(shù)組即是共享資源,當(dāng)多線程對(duì)此數(shù)據(jù)進(jìn)行操作的時(shí)候如果不進(jìn)行同步控制,即有可能會(huì)出現(xiàn)線程安全問題。
1.2 add方法可能出現(xiàn)的問題分析
首先我們看一下add的源碼如下:
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
此方法中有兩個(gè)操作,一個(gè)是數(shù)組容量檢查,另外就是將元素放入數(shù)據(jù)中。我們先看第二個(gè)簡(jiǎn)單的開始分析,當(dāng)多個(gè)線程執(zhí)行順序如下所示的時(shí)候,會(huì)出現(xiàn)最終數(shù)據(jù)元素個(gè)數(shù)小于期望值。
按照此順序執(zhí)行完之后,我們可以看到,elementData[n]的只被設(shè)置了兩次,第二個(gè)線程設(shè)置的值將前一個(gè)覆蓋,最后size=n+1。下面使用代碼進(jìn)行驗(yàn)證此問題。
1.3 代碼驗(yàn)證
首先先看下以下代碼,開啟1000個(gè)線程,同時(shí)調(diào)用ArrayList的add方法,每個(gè)線程向ArrayList中添加100個(gè)數(shù)字,如果程序正常執(zhí)行的情況下應(yīng)該是輸出:
list size is :10000
代碼如下:
private static List<Integer> list = new ArrayList<Integer>(); private static ExecutorService executorService = Executors.newFixedThreadPool(1000); private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 100; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args){ for(int i=0; i < 1000; i++){ executorService.submit(new IncreaseTask()); } executorService.shutdown(); while (!executorService.isTerminated()){ try { Thread.sleep(1000*10); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("All task finished!"); System.out.println("list size is :" + list.size()); }
當(dāng)執(zhí)行此main方法后,輸出如下:
從以上執(zhí)行結(jié)果來看,最后輸出的結(jié)果會(huì)小于我們的期望值。即當(dāng)多線程調(diào)用add方法的時(shí)候會(huì)出現(xiàn)元素覆蓋的問題。
1.4 數(shù)組容量檢測(cè)的并發(fā)問題
在add方法源碼中,我們看到在每次添加元素之前都會(huì)有一次數(shù)組容量的檢測(cè),add中調(diào)用此方法的源碼如下:
ensureCapacityInternal(size + 1);
容量檢測(cè)的相關(guān)源碼如下:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
容量檢測(cè)的流程圖如下所示:
我們以兩個(gè)線程執(zhí)行add操作來分析擴(kuò)充容量可能會(huì)出現(xiàn)的并發(fā)問題:
當(dāng)我們新建一個(gè)ArrayList時(shí)候,此時(shí)內(nèi)部數(shù)組容器的容量為默認(rèn)容量10,當(dāng)我們用兩個(gè)線程同時(shí)添加第10個(gè)元素的時(shí)候,如果出現(xiàn)以下執(zhí)行順序,可能會(huì)拋出java.lang.ArrayIndexOutOfBoundsException異常。
第二個(gè)線程往數(shù)組中添加數(shù)據(jù)的時(shí)候由于數(shù)組容量為10,而此操作往index為10的位置設(shè)置元素值,因此會(huì)拋出數(shù)組越界異常。
1.5 代碼驗(yàn)證數(shù)組容量檢測(cè)的并發(fā)問題
使用如下代碼:
private static List<Integer> list = new ArrayList<Integer>(3); private static ExecutorService executorService = Executors.newFixedThreadPool(10000); private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 1000000; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args){ new IncreaseTask().start(); new IncreaseTask().start(); }
執(zhí)行main方法后,我們可以看到控制臺(tái)輸出如下:
1.6 ArrayList中其他方法說明
ArrayList中其他包含對(duì)共享變量操作的方法同樣會(huì)有并發(fā)安全問題,只需要按照以上的分析方法分析即可。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。