ArrayList爲什麼是線程不安全的(如果不對數據處理,直接取數那是沒問題)

模擬測試給list加入10000條數據,代碼:

public class UnsafeList {
    public static void main(String[] args) {
        // 進行 10次測試
        for (int i = 0; i < 10; i++) {
            test();
        }
    }
 
    public static void test() {
        // 用來測試的List
        List<Object> list = new ArrayList<Object>();
        // 線程數量(100)
        int threadCount = 100;
        // 用來讓主線程等待threadCount個子線程執行完畢
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        // 啓動threadCount個子線程
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new MyThread(list, countDownLatch));
            thread.start();
        }
        try {
            // 主線程等待所有子線程執行完成,再向下執行
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // List 的size
        System.out.println(list.size());
    }
}
 
class MyThread implements Runnable {
    private List<Object> list;
    private CountDownLatch countDownLatch;
 
    public MyThread(List<Object> list, CountDownLatch countDownLatch) {
        this.list = list;
        this.countDownLatch = countDownLatch;
    }
 
    public void run() {
        // 每個線程向List中添加100個元素
        for (int i = 0; i < 1000; i++) {
            list.add(new Object());
        }
        // 完成一個子線程(主線程等待子線程執行完了再執行)
        countDownLatch.countDown();
    }
}
代碼轉載:https://www.cnblogs.com/WuXuanKun/p/5556999.html


打印結果:

100000
100000
99847
100000
99670
99442
99998
100000
99271
99926

由此可見是ArrayList做add操作時候,會丟失一些數據,所以所Array是線程不安全的。

那麼爲什麼導致漏掉一些數據呢?

來看看ArrayList.add方法


 
// Object[] elementData:ArrayList的數據結構是數組類型,list存放的數據就是存放在elementData裏面的
// 第1步
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // list的size+1
	elementData[size++] = e; // 將數據放到數組最後一個
	return true;
}
 
 
// 第2步,元素有變化,那麼就調用ensureExplicitCapacity方法
private void ensureCapacityInternal(int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
 
	// 進入ensureExplicitCapacity方法
	ensureExplicitCapacity(minCapacity);
}
 
 
// 第3步,元素有變化,那麼就調用grow方法
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
	// elementData:list的數組元素
	// minCapacity: add操作後的容量
	if (minCapacity - elementData.length > 0) 
		grow(minCapacity);
}
 
 
// 第4步
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 爲什麼要-8,是因爲有些虛擬機有一些hear的key
private void grow(int minCapacity) {
 
	// 原始list的容量(容量不是list.size)
	int oldCapacity = elementData.length; 
	
	//現在list的容量,此時是做講原始容量擴大0.5倍,oldCapacity >> 1:2進制右位移,就是除以2的意思
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	// 一般不會進入hugeCapacity這個方法,
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
		
	// 複製elementData返回一個新的數組對象,這個時候list.add完成
	elementData = Arrays.copyOf(elementData, newCapacity);
}

分析爲什麼會add丟失呢?

List對象,做add時,第1步到第3步,都不會改變elementData對象,只有在第4步Arrays.copyOf的時候,返回一個新的數組對象
因此:當有線程t1、t2同時進入grow方法,兩個線程都會執行Arrays.copyOf方法,返回2個不同的elementData對象,
假如,t1先返回,t2後返回,那麼List.elementData == t1.elementData,
然後t2也返回後,這時List.elementData == t2.elementData
這時,t2.elementData就把t1.elementData數據給覆蓋了。導致t1.elementData被丟失

這就是ArrayList爲什麼線程不安全的原因


java面試也會問到這些問題,

1、ArrayList是不是線程不安全的?不是

2、ArrayList爲什麼是線程不安全的?

3、ArrayList擴容原理?每次擴容是原來size的0.5倍

4、Arrays.copyOf返回的是原始對象、還是新對象?新對象

5、如果讓ArrayList變成線程安全的?
List list1 = Collections.synchronizedList(new ArrayList());

或者用List list1 = new Vector();

如何解決線程不安全?

一:使用synchronized關鍵字,這個大家應該都很熟悉了,不解釋了;

二:使用Collections.synchronizedList();使用方法如下:

        假如你創建的代碼如下:List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();

        那麼爲了解決這個線程安全問題你可以這麼使用Collections.synchronizedList(),如:

       List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());

       其他的都沒變,使用的方法也幾乎與ArrayList一樣,大家可以參考下api文檔;

額外說下 ArrayList與LinkedList;這兩個都是接口List下的一個實現,用法都一樣,但用的場所的有點不同,ArrayList適合於進行大量的隨機訪問的情況下使用,LinkedList適合在表中進行插入、刪除時使用,二者都是非線程安全,解決方法同上(爲了避免線程安全,以上採取的方法,特別是第二種,其實是非常損耗性能的)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章