ArrayList執行緒不安全詳解
阿新 • • 發佈:2019-02-06
首先需要了解什麼是執行緒安全:執行緒安全就是說多執行緒訪問同一程式碼(物件、變數等),不會產生不確定的結果。 既然說ArrayList是執行緒不安全的,那麼在多執行緒中操作一個ArrayList物件,則會出現不確定的結果。具體是怎樣不確定,請看測試下面這段程式碼(在此測試ArrayList的add方法):
執行幾次會發現結果不一樣,甚至有時會出現ArrayIndexOutOfBoundsException,貼出幾次執行的結果:public class ArrayListInThread implements Runnable{ //執行緒不安全 private List threadList = new ArrayList(); //執行緒安全 //private List threadList = Collections.synchronizedList(new ArrayList()); @Override public void run() { try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } //把當前執行緒名稱加入list中 threadList.add(Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException{ ArrayListInThread listThread = new ArrayListInThread(); for(int i = 0; i < 100; i++){ Thread thread = new Thread(listThread, String.valueOf(i)); thread.start(); } //等待子執行緒執行完 Thread.sleep(2000); System.out.println(listThread.threadList.size()); //輸出list中的值 for(int i = 0; i < listThread.threadList.size(); i++){ if(listThread.threadList.get(i) == null){ System.out.println();; } System.out.print(listThread.threadList.get(i) + " "); } } }
結果一:
結果二:
結果三:
以上執行結果說明ArrayList確實是執行緒不安全的,然後我們從執行緒併發的角度分析,為何會出現這樣的結果:
首先,我們先來看一下ArrayList中的add方法是如何實現的(檢視詳細原始碼請點選):
結果中,有的值沒有出現(結果一中3沒有出現),有的出現了null值,這是由於賦值時出現了覆蓋。賦值語句為:elementData[size++] = e,這條語句可拆分為兩條: 1. elementData[size] = e; 2. size ++; 假設A執行緒執行完第一條語句時,CPU暫停執行A執行緒轉而去執行B執行緒,此時ArrayList的size並沒有加一,這時在ArrayList中B執行緒就會覆蓋掉A執行緒賦的值,而此時,A執行緒和B執行緒先後執行size++,便會出現值為null的情況;至於結果三中出現的ArrayIndexOutOfBoundsException異常, 則是A執行緒在執行ensureCapacity//新增元素e public boolean add(E e) { // 確定ArrayList的容量大小 ensureCapacity(size + 1); // Increments modCount!! // 新增e到ArrayList中 elementData[size++] = e; return true; } // 確定ArrarList的容量。 // 若ArrayList的容量不足以容納當前的全部元素,設定 新的容量=“(原始容量x3)/2 + 1” public void ensureCapacity(int minCapacity) { // 將“修改統計數”+1,該變數主要是用來實現fail-fast機制的 modCount++; int oldCapacity = elementData.length; // 若當前容量不足以容納當前的元素個數,設定 新的容量=“(原始容量x3)/2 + 1” if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; //如果還不夠,則直接將minCapacity設定為當前容量 if (newCapacity < minCapacity) newCapacity = minCapacity; elementData = Arrays.copyOf(elementData, newCapacity); } }