ArrayList为什么是线程不安全的
2018年01月19日 19:05:23 Tonny__ 阅读数:1536
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zc375039901/article/details/79109930
模拟测试给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();