ArrayList源码之为何线程不安全

解决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

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

没复现出来,待补充。

Java~源码分析为什么ArrayList是线程不安全的, 如何将其变为线程安全的 最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份。 阅读详情

相关推荐

【ros】ROS1的安装和相关使用方法

主要学习ros相关内容

Greary的博客 2378

【JAVA】线程不安全问题以及相关解决方案

返回到用两个线程自增count10w次的代码,也就是让每个线程都独立完成自己的任务后,另外的线程才开始调度执行,所以我们要对线程1进行加锁,当线程1正在运行的时候,线程2乖乖的阻塞等待带xianc1解锁,线程1解锁后线程2开始执行调度,线程2也要进行加锁,所以线程2在执行调度的时候,线程1反过来阻塞等待。因此线程1与线程2会产生“锁竞争”,也就是线程1还没解锁,线程2不动,线程2执行时还没解锁,线程1不动,当然,哪个线程先执行由内核说了算,谁先执行,另外的就阻塞等待。

qq_61862008的博客 1248

[小梅哥FPGA] 如何通过动态重配置实现FPGA时钟的实时频率/相位/占空比调整?

PLL和MMCM是实际存在的硬件电路,用以对输入时钟的负面变化,如抖动、频率变化、相位偏移、占空比失真等进行处理。基于该表,用户在初始化Clk_Wiz后,只需要计算好所需的频率、相位、占空比对应的设置值,使用Xil_Out32函数配置对应寄存器即可。通过上述步骤,在平时的开发调试过程中,亦或是一些对于时钟可能有着实时性要求的设计(如数据采集、图像处理)里,我们便可以在无需重新修改设计的前提下,通过AXI4总线来完成对时钟频率、相位的调控,从而达到缩短设计周期以及让设计适应更多场景的目的。

zgmxs的专栏 2493

java多线程并发之旅-16-双端队列之 ArrayDeque

ArrayDeque ArrayDeque不是线程安全的。 ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。 当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。 特性 无容量大小限制,容量按需增长; 非线程安全队列,无同步策略,不支持多线程安全访问; 当用作栈时,性能优于Stack,当用于队列时,性能优于...

1万+

通过arcmap制作的第一张流域图

需要知道,识别河流依次需要填洼,流向,流量,栅格计算器,河流链接,河网分级.通过卫星图确定河流入口,在入口处画一个点,注意不要与其他河流相交。随后是进行对河流的筛选使用con函数,筛选流量大于100的河流。我们使用镶嵌工具-->镶嵌至新栅格 将两个数据合并。填洼后如图,随后进行流向的获得.使用流向工具。首先导入准备好的数据,可见两张图片并非一体。教程参考b站up:爱画地图的boney。惊醒栅格河网矢量化,进行河流分级。输入表面栅格数据,等待后如图。本次制作的是双峰县的流域图。输入流向数据,输出后如图。

pianmian1的博客 2225

视觉SLAM与激光SLAM简单对比分析

本文旨在梳理目前较为前沿的SLAM技术,包括激光和视觉,主要从精度和实时性两个方面对算法进系评价。对于激光SLAM了解不深,后期需要补充相关算法的核心思想与算法框架。有问题请大佬们随时留言,我再改正。

超爱吃小蛋糕的66的博客 3854

ArrayDeque源码分析

ArrayDeque不是线程安全的。  ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。  当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。  1. 两个重要的索引:head和tail  Java代码   // 第一个元素的索引   private transient int head;

喜欢讨论、分享 1120

CDR学习笔记——02

这篇写一些高级CDR的架构原理。

... 1015

线程安全 && 线程不安全

线程安全对象:ArrayList,LinkedList,HashMap,StringBuilder ArrayList在内部使用了数组来存储所有元素; LinkedList通过“链表”也实现了List接口,在LinkedList中,它的内部每个元素都指向下一个元素 HashMap是一个散列表,它存储的内容是键值对(key-value)映射。 该类实现了Map接口,根据键的HashC...

luxuiary的博客 236

VS Code下载安装与使用教程

VSCode下载安装与使用教程

weixin_46713695的博客 4万+

高速特征金字塔多角度旋转模板匹配(工业零部件缺陷检测C++ Opencv版)

博主在性能较好的笔记本上测试算法全部执行效果大约为45ms,在一般配置的笔记本上博主在性能较好的笔记本上测试算法全部执行效果大约为130ms。下图即为实际检测效果。本文最后附有完整代码:Qt版和MFC版本(可直接运行)。...

认真努力,做一只会飞的毛毛虫。 5547

源码分析为什么ArrayList是线程不安全的

源码分析为什么ArrayList是线程不安全的

weixin_50989637的博客 1526

【WorkBuddy专栏50】代码开发技术体系深度分析——前端、后端、全栈、移动端、数据工程,WB和CODEBUDDY谁更擅长?

【WorkBuddy专栏50】代码开发技术体系深度分析——前端、后端、全栈、移动端、数据工程,WB和CODEBUDDY谁更擅长?

AI人人懂 237

ArrayList线程不安全的个人源码分析

1、ArrayList线程不安全的三种结果 (1)某些信息没有添加成功(丢失了一些数据) (2)下标越界 (3)Null 分析 1)丢失数据 先看ArrayList.add()的源码 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } 可以得出Arra

btopen的博客 258

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

arraylist 并发_ArrayList为什么线程不安全 源码角度分析其具体原因

前言我们都知道ArrayList是线程不安全的,那么它不安全在哪里?又会出现什么并发问题呢?ArrayList继承结构树ArrayList继承结构树一、ArrayList源码摘录public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializab...

weixin_42387319的博客 175

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

ArrayList(线程不安全,源码)

ArrayList线程不安全 不安全事例代码 public static void main(String[] args) { final ArrayList<Integer> arrayList = new ArrayList<>(); for(int i=0;i<10000;i++){ final int ...

qq_31652941的博客 197

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

(Java源码阅读) ArrayList为什么线程不安全?

Java源码上分析ArrayList为什么线程不安全?

4479

源码角度分析Java ArrayList为什么是线程不安全的

面试中一个经常被问到的问题就是:ArrayList是否是线程安全的? 答案当然很简单,无论是背来的还是自己看过源码,我们都知道它是线程不安全的。那么它为什么是线程不安全的呢?它线程不安全的具体体现又是怎样的呢?我们从源码的角度来看下。 public class ArrayList<E> extends AbstractList<E> implements ...

黎先生的博客 432

ArrayList为什么线程不安全 线程不安全体现在哪些方面 源码角度分析其具体原因

我们都知道ArrayList是线程不安全的,那么它不安全在哪里?又会出现什么并发问题呢?

wandou9527的博客 2414

【JDK源码】解决ArrayList线程不安全

ArrayList 在并发情况下是不安全的! 解决方案: 1、切换成Vector就是线程安全的啦! public class ListTest { public static void main(String[] args) { List<Object> arrayList = new Vector<>(); } } Vector的add方法,Vector比ArrayList更早出现,所以Vector只是其中一种解决办法 2、使用工具类 Colle

早上真起不来的博客 233

arraylist 线性不安全_为什么说ArrayList是线程不安全的?

为什么说ArrayList是线程不安全的?Zorrooooo 2017-10-11 17:52:21 26500 收藏 16展开一.概述对于ArrayList,相信大家并不陌生。这个类是我们平时接触得最多的一个列表集合类。面试时相信面试官首先就会问到关于它的知识。一个经常被问到的问题就是:ArrayList是否是线程安全的?答案当然很简单,无论是背来的还是自己看过源码,我们都知道它是线程不安全...

weixin_39889329的博客 90

ArrayList HashSet HashMap 的线程不安全实例显示,及改进方案和源码解析

ArrayList public class ThreadTest { public static void main(String[] args) { List&lt;String&gt; list = new ArrayList&lt;&gt;();//CopyOnWriteArrayList(); for (int i =1; i&lt;=3...

donkeyboy001的博客 226

ArrayList和HashSet线程不安全?源码解析及解决方案

线程不安全问题(ArrayList) 线程不安全案例(Fast-fail) 向ArrayList中添加随机元素,循环20轮 public class test { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 20; i++) { new Thread(

mqq2502513332的博客 374
上一篇: idea注释模板设置
下一篇: java克隆
ludkjava
博客等级 码龄11年 173粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值