概述
执行下面代码,程序何如??
package VolatilePkg;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
* @author Heian
* @time 19/01/22 10:27
* @copyright(C) 2019 深圳市北辰德科技股份有限公司
* 用途:ArrayList 线程为什么不安全?
*/
public class VolatileTest {
static List<Integer> list = new ArrayList<> ();
//static List<Integer> list = new Vector<> ();
public void add(){
for (int i=0;i<1000;i++){
list.add (1);
}
}
public static void test() throws InterruptedException{
VolatileTest vo = new VolatileTest ();
IntStream.range (0,5).forEach (value -> new Thread (() -> vo.add (),String.valueOf (value)).start ());
while (Thread.activeCount ()>1){
Thread.yield ();
}
System.out.println (list.size ());
}
public static void main(String[] args) throws InterruptedException{
VolatileTest.test ();
}
}
如果线程安全则输出为5000,但输出结果有三种情况:
- 小于5000
- 5000
- 小于5000 + ArrayIndexOutOfBoundsException
可以看出ArrayList在多个线程操作的情况下,无法保证数据的完整性。对add源码分析:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 1
elementData[size++] = e; // 2
return true;
}
ArrayList 的不安全主要体现在两个方面:摘自:https://www.jianshu.com/p/41be1efe5d65
原因一:标记2处不是一个原子操作,JMM只保证了基本读取和赋值的原子性,其它均不保证,此可以拆分为两步骤:
elementData[size] = e;
size++;
单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。
- 列表为空 size = 0。
- 线程 A 执行完
elementData[size] = e;
之后挂起。A 把 "a" 放在了下标为 0 的位置。此时 size = 0。 - 线程 B 执行
elementData[size] = e;
因为此时 size = 0,所以 B 把 "b" 放在了下标为 0 的位置,于是刚好把 A 的数据给覆盖掉了。 - 线程 B 将 size 的值增加为 1。
- 线程 A 将 size 的值增加为 2。
这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 "a" 在下标为 0 的位置,"b" 在标为 1 的位置。而实际情况确是下标为 0 的位置为 "b",下标为 1 的位置啥也没有,为null
原因二:标记1处则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,因此便出现了数组越界异常。比如:ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。执行步骤如下:
- 线程 A 执行完 add 函数中的
ensureCapacityInternal(size + 1)
挂起了。 - 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 "b" 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
- 线程 A 接着执行,尝试把 "a" 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常
最后
以上就是笨笨荔枝为你收集整理的ArrayList为什么线程不安全的全部内容,希望文章能够帮你解决ArrayList为什么线程不安全所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复