ArrayList線程不安全分析

        以前一直據說ArrayList線程不安全的說法,百度了一下不安全的分析和列子,感受都不太滿意,最後邊實在不行只好本身分析了。 java

一、多線程插入測試

        代碼實例ArrayListTestDemo1.java: 安全

package com.hjr.back16.common.util;

import static java.lang.System.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 測試ArrayList線程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo1 {
	public static Counter counter = new Counter();
	
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
//		for (int i = 0; i < 10000; i++) {
//			list.add(i);
//		}
		
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++) {
						list.add(1000*j + j); // 結果中拋出異常的代碼
						counter.increment();
					}
				}
			}.start();
		}
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		out.println("list size: " + list.size());
		out.println("Counter: " + counter.getValue());
	}
}

class Counter {
	
	private int value;

	public synchronized int getValue() {
		return value;
	}

	public synchronized int increment() {
		return ++value;
	}

	public synchronized int decrement() {
		return --value;
	}
}
        Demo執行結果:
Exception in thread "Thread-5" Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 51
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 54
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 50
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 52
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
list size: 4858
Counter: 5050
        分析執行結果的時候,咱們可能會發現兩個問題:1)爲何拋出異常;2)爲何list size爲何比Counter小?

        1)爲何拋出異常 多線程

        咱們先看下ArrayList.add()方法的源碼: app

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
        上邊源碼中,ensureCapacityInternal方法主要是判斷須要的容量(size+1)是否大於 ArrayList的容量(elementData.length),若是大於 根據規則增長ArrayList的容量,不然 直接返回

        因爲ArrayList.add()沒有同步鎖,因此多線程調用這個方法的時候,有可能多個線程拿到相同的size值去與ArrayList的容量作比較,而執行到elemntData[size++] = e;時倒是有序的,這時,因爲ensureCapacityInternal()沒有適當的擴大ArrayList的容量,從而致使插入數據的長度大於ArrayList的剩餘容量,因而也就拋出了越界的異常(java.lang.ArrayIndexOutOfBoundsException),圖解: 測試


        2)爲何list size爲何比Counter小 this

        對於這個問題,須要說明的是如代碼所示,Counter.increment()方法是加了同步鎖的,因此Counter輸出的值就是list.add()成功的次數。然而,list size比Counter小,一樣是由於多線程進入add方法時,拿到相同的size執行elementData[size++] = e;這句代碼,而Java的自增操做又不是原子性操做,這樣就致使了多個線程拿相同的size執行加1的操做,最後邊就出現了list size小於Counter,也就小於世界成功執行add()方法次數的問題。 spa

二、迭代器遍歷List時作修改

        代碼實例ArrayListTestDemo2.java: 線程

package com.hjr.back16.common.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 測試ArrayList線程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo2 {
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < 10000; i++) {
			list.add(i);
		}
		
		// 列表遍歷線程
		new Thread() {
			public void run() {
				Iterator<Integer> iterator = list.iterator();
				while(iterator.hasNext()) {
					iterator.next(); // 異常拋出的地方
				}
			}
		}.start();
		
		// 列表新增元素線程
		new Thread() {
			public void run() {
				for (int j = 0; j < 1000; j++) {
					list.add(1000*j + j);
				}
			}
		}.start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
        Demo執行結果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
	at java.util.ArrayList$Itr.next(ArrayList.java:831)
	at com.hjr.back16.common.util.ArrayListTestDemo2$1.run(ArrayListTestDemo2.java:25)
        異常來源代碼片斷ArrayList內部類Itr:
/**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        ... ...

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
        根據異常信息,咱們知道異常是Itr.checkForComodification()這個方法拋出的 。其中,modCount是一個ArrayList用於記錄ArrayList修改(包括列表元素的增刪改等)次數的成員變量;expectedModCount是初始話一個新的Iterator時的modCount值。整個方法主要用於判斷ArrayList在Iterator遍歷的過程當中,是否發生修改,若是發生修改expectedModCount與modCount不一樣),拋出異常(這實際上是Java集合的一種錯誤機制:fail-fast)。
相關文章
相關標籤/搜索