美文网首页
ArrayList为何线程不安全

ArrayList为何线程不安全

作者: 手打丸子 | 来源:发表于2019-01-22 17:42 被阅读0次

    JAVA8以前的我就不管了,我手上只有JAVA8以上的环境;
    文末有小技巧,如何获得线程安全又高效的list
    顺便链接下:HashMap为何线程不安全

    对于容器而言,有以下几个动作:增、删、改、查、扩容;
    ArrayList的几个动作:增、删、查、扩容;
    我们知道ArrayList是线程不安全的,测试代码(使用并发流产生并发)如下:

    try {
                List<Integer> list1 = new ArrayList();
                List<Integer> list2 = new ArrayList<>();
                for (int i = 0; i < 1000; i++) {
                    list1.add(i);
                }
                list1.parallelStream().forEach(
                        i -> {
                            try {
                                list2.add(i);
                            }catch (Exception e){
                                System.out.println(e);
                            }
                        }
                );
                System.out.println("size of list2:" + list2.size());
            } catch (Exception e) {
                System.out.println(e);
       }
    
    java.lang.ArrayIndexOutOfBoundsException: 549
    size of list2:959
    

    我们可以看到,list2不仅数量没有数量没有对
    还时常伴随着java.lang.ArrayIndexOutOfBoundsException

    即使我们初始化好容量

    try {
                List<Integer> list1 = new ArrayList();
                List<Integer> list2 = new ArrayList<>(1000);
                for (int i = 0; i < 1000; i++) {
                    list1.add(i);
                }
                list1.parallelStream().forEach(
                        i -> {
                            try {
                                list2.add(i);
                            }catch (Exception e){
                                System.out.println(e);
                            }
                        }
                );
                System.out.println("size of list2:" + list2.size());
            } catch (Exception e) {
                System.out.println(e);
            }
    
    size of list2:983
    

    确实好一点,起码不报异常了,数量也更多了,但还是错了;
    上面两个测试起码说明了几点:

    1.扩容的时候线程不安全,搞不好会搞出异常来;
    2.新增动作也是不安全的,
    3.删和查想来肯定也不是线程安全的;
    

    我们来看看源代码,各个动作都干了啥;
    ArrayList<Integer> list2 = new ArrayList<>();
    首先看初始化:

    public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    

    ArrayList是基于数组的,所以内部有个transient Object[] elementData;
    不指定容量的时候,JAVA8中直接初始化了一个空数组
    this.elementData = EMPTY_ELEMENTDATA;
    初始化到此结束,来看看add的时候干了啥:

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

    先不管ensureCapacityInternal
    elementData[size++] = e;就是个很不安全的动作,因为private int size;并不是线程安全的,所以比如现在list中有900个元素,list1和list2完全有可能再增加的时候都往901个里写,那就GG了;

    我们再来看下扩容:

    private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    恩,看到这我都不想往下看了;
    总的来说,读容量,对容量进行增长时,各个变量都没有线程安全,都容易发生并发问题,所以这里多线程出问题的地方就多了;
    这是一个彻彻底底不安全的容器;

    读我就不写了,总之这个容器类压根没线程安全
    要想安全地使用ArrayList,只有一个办法:
    请在单线程中使用ArrayList

    但是经常需要使用到类似List容器,那么这里有个小技巧
    如果你要放的是基础变量且恰巧不重复:
    Set<Integer> list2 = ConcurrentHashMap.newKeySet();
    是你的最佳选择;
    其他小技巧待补充.....用ConcurrentHashMap来绕一下逻辑是比较推荐的

    相关文章

      网友评论

          本文标题:ArrayList为何线程不安全

          本文链接:https://www.haomeiwen.com/subject/lslrjqtx.html