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 list = new ArrayList();
        // 线程数量(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 list;
    private CountDownLatch countDownLatch;

    public MyThread(List 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();

你可能感兴趣的:(java基础,Java集合)