专栏首页java相关ArrayList源码和多线程安全问题分析

ArrayList源码和多线程安全问题分析

1.ArrayList源码和多线程安全问题分析

在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析。

1.1 数据结构

ArrayList内部是使用数组保存元素的,数据定义如下:

transient Object[] elementData; // non-private to simplify nested class access

在ArrayList中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控制,即有可能会出现线程安全问题。

1.2 add方法可能出现的问题分析

首先我们看一下add的源码如下:

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

此方法中有两个操作,一个是数组容量检查,另外就是将元素放入数据中。我们先看第二个简单的开始分析,当多个线程执行顺序如下所示的时候,会出现最终数据元素个数小于期望值。

按照此顺序执行完之后,我们可以看到,elementData[n]的只被设置了两次,第二个线程设置的值将前一个覆盖,最后size=n+1。下面使用代码进行验证此问题。

1.3 代码验证

首先先看下以下代码,开启1000个线程,同时调用ArrayList的add方法,每个线程向ArrayList中添加100个数字,如果程序正常执行的情况下应该是输出:

list size is :10000  

代码如下:

private static List<Integer> list = new ArrayList<Integer>();

    private static ExecutorService executorService = Executors.newFixedThreadPool(1000);

    private static class IncreaseTask extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
            for(int i =0; i < 100; i++){
                list.add(i);
            }
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
        }
    }

    public static void main(String[] args){
        for(int i=0; i < 1000; i++){
            executorService.submit(new IncreaseTask());
        }
        executorService.shutdown();
        while (!executorService.isTerminated()){
            try {
                Thread.sleep(1000*10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("All task finished!");
        System.out.println("list size is :" + list.size());
    }

当执行此main方法后,输出如下:

从以上执行结果来看,最后输出的结果会小于我们的期望值。即当多线程调用add方法的时候会出现元素覆盖的问题。

1.4 数组容量检测的并发问题

在add方法源码中,我们看到在每次添加元素之前都会有一次数组容量的检测,add中调用此方法的源码如下:

ensureCapacityInternal(size + 1);

容量检测的相关源码如下:

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);
}

容量检测的流程图如下所示:

我们以两个线程执行add操作来分析扩充容量可能会出现的并发问题: 当我们新建一个ArrayList时候,此时内部数组容器的容量为默认容量10,当我们用两个线程同时添加第10个元素的时候,如果出现以下执行顺序,可能会抛出java.lang.ArrayIndexOutOfBoundsException异常。

第二个线程往数组中添加数据的时候由于数组容量为10,而此操作往index为10的位置设置元素值,因此会抛出数组越界异常。

1.5 代码验证数组容量检测的并发问题

使用如下代码: private static List list = new ArrayList(3);

    private static ExecutorService executorService = Executors.newFixedThreadPool(10000);

    private static class IncreaseTask extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
            for(int i =0; i < 1000000; i++){
                list.add(i);
            }
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
        }
    }

    public static void main(String[] args){

        new IncreaseTask().start();
        new IncreaseTask().start();
    }

执行main方法后,我们可以看到控制台输出如下:

1.6 ArrayList中其他方法说明

ArrayList中其他包含对共享变量操作的方法同样会有并发安全问题,只需要按照以上的分析方法分析即可。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringBoot中Async异步方法和定时任务介绍

    代码改变世界-coding
  • HttpServlet源码分析

    代码改变世界-coding
  • JDK线程池分析和使用

    代码改变世界-coding
  • 设计模式之单例模式

    单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内...

    用户1205080
  • synchronized关键字和volatile关键字的区别

    线程原子性是指不能在被拆分的操作。在说的直白点就是我们知道线程在执行时是需要一个前提条件的那就是需要获取到系统CPU的执行资格,虽然线程获取到了执行资格但CPU...

    吉林乌拉
  • 请说出你所知道的线程同步的方法

    (2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。

    剑走天涯
  • BIO,NIO,AIO总结

    Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的...

    崔笑颜
  • 多线程编程:多线程并发制单的开发记录【一】

    进程和线程: 下图是在来自知乎用户的解释,个人感觉狠到位 ?        进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资...

    赵小忠
  • Java 虚拟机对锁优化所做的努力

    作为一款公用平台,JDK 本身也为并发程序的性能绞尽脑汁,在 JDK 内部也想尽一切办法提供并发时的系统吞吐量。这里,我将向大家简单介绍几种 JDK 内部的 "...

    Java技术栈
  • 一次linux中定位c++程序运行异常的经历

    今天下午我遇到了一些棘手的问题,因为在mips64上编译程序,经常出现程序编译不出来,或者运行不正常,花了很长的时间定位,最后和同事一些解决了,下面分享出来我提...

    机智的程序员小熊

扫码关注云+社区

领取腾讯云代金券