ArrayList源码之为何线程不安全

摘要

面试的时候经常会问,ArrayList为何线程不安全,本篇文章通过分析源码、测试类复现现问题的形式,解释ArrayList线程不安全的原因。

源码分析

ArrayList add()方法的代码如下:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);//数组是否够大,不够大则扩容
        elementData[size++] = e;//数组赋值,集合大小加一
        return true;//返回添加成功标识
    }

add方法中有三行代码,其中第二行代码实际上执行分为两行,拆分后如下:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);//数组是否够大,不够大则扩容
        elementData[size] = e;//数组赋值
        size=size+1;//集合大小加一
        return true;//返回添加成功标识
    }

分析这段代码,ArrayList的add方法有两种线程不安全的情况:

  • 数组越界
  • 一个线程的值覆盖另一个线程添加的值

数组越界

  • 列表大小为9,即size=9。数组大小为10,即elementData的大小就为10

  • 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。

  • 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。

  • 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。

  • 线程B也发现需求大小为10,也可以容纳,返回。

  • 线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。

  • 线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException

一个线程的值覆盖另一个线程添加的值

  • 列表大小为0,即size=0
  • 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
  • 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
  • 线程A开始将size的值增加为1
  • 线程B开始将size的值增加为2

这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标1的位置上什么都没有。并且后续除非使用set方法修改此位置的值,否则将一直为null,因为size为2,添加元素时会从下标为2的位置上开始。

##测试类复现问题

数组越界

  1. 新建线程,向list中循环添加数据
package org.ludk.arraylistsafe;

import java.util.List;

/**
 * @author ludengke
 * @title: AddDataThread
 * @projectName springcloud-demo
 * @description: TODO
 * @date 2021/10/1714:11
 */
public class AddDataThread implements Runnable{
    private List<Integer> myList;

    public AddDataThread(List<Integer> myList) {
        this.myList = myList;
    }
    /**
    *@Description
    *@Param []线程的run方法,循环向集合中添加数据
    *@Return void
    *@Author ludengke
    *@Date 2021/10/17
    *@Time 14:14
    */
    public void run() {
        for (int i=0;i<100;i++){
            myList.add(i);
            System.out.println(myList.get(i));
        }
    }
}

  1. 新建测试类,new两个线程向一个list中添加数据
package org.ludk.arraylistsafe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * ArrayList线程安全问题测试类
 */
public class TestMain {
    public static void main(String[] args) {
        testListSafe();
    }
    public static void testListSafe(){
        List<Integer> myList=new ArrayList<Integer>();
        new Thread(new AddDataThread(myList)).start();
        new Thread(new AddDataThread(myList)).start();
        System.out.println("期望的集合大小为200,实际大小为:"+myList.size());
    }
    public static void testListSafe2(){
        final List<String> myList=new ArrayList<String>();
        for (int i=0;i<500;i++){
            new Thread(new Runnable() {
                public void run() {
                    myList.add(UUID.randomUUID().toString().substring(0, 8));
//                    System.out.println(myList);
                }
            }).start();
        }
    }
}

  1. 运行测试类,查看结果
D:\work\java\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=53865:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\target\classes org.ludk.arraylistsafe.TestMain
期望的集合大小为200,实际大小为:0
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 10
	at java.util.ArrayList.add(ArrayList.java:463)
	at org.ludk.arraylistsafe.AddDataThread.run(AddDataThread.java:28)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

一个线程的值覆盖另一个线程添加的值

没复现出来,待补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值