如果非要在多线程中使用 ArrayList 会发生什么?

解决ArrayList在多线程操作中线程不安全导致的报错 阅读详情

为了便于理解,当时只是通过代码执行顺序说明了异常原因。其实多线程中还会涉及 Java 内存模型,本文就从这方面说明一下。

对比源码

我们先来看看 Java 11 中, add 方法做了什么调整。

Java 8 中 add 方法的实现:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
Java 11 中 add 方法的实现:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

两段逻辑的差异在于数组下标是否确定:

  • elementData[size++] = e; ,Java 8 中直接使用 size 定位并赋值,然后通过 size++ 自增
  • elementData[s] = e; size = s + 1; ,Java 11 借助临时变量 s 定位并赋值,然后通过 size = s + 1 给 size 赋新值

Java 11 的优点在于,为数组指定元素赋值的时候,下标值是确定的。也就是说,只要进入 add(E e, Object[] elementData, int s) 方法中,就只会处理指定位置的数组元素。并且, size 的值也是根据 s 增加。按照执行顺序推断,最终的结果可能会丢数,但是不会出现 null。(多个线程向同一个下标赋值,即 s 相等,那最终 size 也相等。)

验证一下

让我们来验证下。

package com.kuaishou.is.datamart;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch waiting = new CountDownLatch(3);
        Thread t1 = new Thread(() -> {
            try {
                latch.await();
                for (int i = 0; i < 1000; i++) {
                    list.add("1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                waiting.countDown();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                latch.await();
                for (int i = 0; i < 1000; i++) {
                    list.add("2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                waiting.countDown();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                latch.await();
                for (int i = 0; i < 1000; i++) {
                    list.add("2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                waiting.countDown();
            }
        });
        t1.start();
        t2.start();
        latch.countDown();
        waiting.await();
        System.out.println(list);
    }
}

在 Java 8 和 Java 11 中分别执行,果然,出现了 ArrayIndexOutOfBoundsException 和 null 的情况。如果没有出现,那就是姿势不对,需要多试几次或者多几个线程。

换个角度想问题

上一篇通过代码执行顺序解释了出现问题的原因,这次再看看 JMM 的原因。

从上图我们可以看到,Java 为每个线程创建了一个本地内存区域,也就是说,代码运行过程中使用的数据,是线程本地缓存的数据。这份缓存的数据,会与主内存的数据做交换(更新主内存数据或更新本次缓存中的数据)。

我们通过一个时序图看下为什么会出现 null(数组越界异常同理):

从时序图我们可以看出现,在执行过程中,两个线程取的 size 值和 elementData 数组地址,大部分是操作自己本地缓存中的,执行一段时间后,会将本地缓存中的数据写回主内存数据,然后还会从主内存中读取最新数据更新本地缓存数据。异常就在这个交换过程中发生了。

这个时候,可能有读者会想,是不是把 size 和 elementData 两个变量加上 volatile 就可以解决了。如果这样想,那你就想简单。线程安全是整个类设计实现时已经确定了,除了属性需要考虑多线程的影响,方法(主要是会修改属性元素的方法)也需要考虑。

ArrayList 的定位是非线程安全的,其中的所有方法都没有考虑多线程下为共享资源加锁。即使 size 和 elementData 两个变量都是实时读写主内存,但是 add 和 grow 方法还是可能会覆盖另一个线程的数据。

我们从 ArrayList 的 add 方法注释可以得知,方法拆分不是为了实现线程安全,而是为了执行效率和内存占用:

This helper method split out from add(E) to keep method bytecode size under 35 (the -XX:MaxInlineSize default value), which helps when add(E) is called in a C1-compiled loop.

所以说,在多线程场景下使用 ArrayList ,该出现的异常,一个也不会少。

如果非要在多线程中使用ArrayList会发生什么? 我们都知道,Java中的ArrayList是非线程安全的,这个知识点太熟了,甚至面试的时候都很少问了。 但是我们真的清楚原理吗?或者知道多线程情况下使用ArrayList会发生什么? 前段时间,我们就踩坑了,而且直接踩了两个坑,今天就来扒一扒。 上源码 上代码之前先说下 ArrayList 的 add 逻辑: 检查队列中数组是否还没有添加过元素 如果是,设置当前需要长度为10,如果否,设置当前需要长度为当前队列长度+1 判断需要长度是否大于数组大小 如果是,需要扩容,将数组长度扩容1.5倍(第一次扩容会从 阅读详情

相关推荐

【异常】多个线程同时尝试修改一个集合(如 ArrayList、HashMap 等),会抛出时java.util.ConcurrentModificationException: null

请求地址’/queryAllMyScenarioCardDetailInfo’,发生未知异常.

奶爸的编程之路,也就一周冷个三天 416

多线程往同一个arrayList中添加元素,会有什么问题?

多线程往同一个arrayList中添加元素,会有什么问题?

森林之王的博客 2300

解决在封装自定义IP时加入ila,或者其他ip核时出错的情况

在查阅很多资料后,明白了这个应该是软件bug,但是最终还是解决了问题 我们只需要删除

weixin_42553916的博客 3604

ArrayList 多线程操作的问题

这些问题的根本原因在于 ArrayList 不是线程安全的。在 Java 集合框架中,ArrayList 的设计没有考虑同步机制,这意味着当多个线程对其进行操作时,并没有内置的方法来防止竞争条件或保证线程安全。

龙大的博客 1291

Simplicity Studio V5与EFR32BG22蓝牙开发环境实战指南

本文详细介绍了Simplicity Studio V5集成开发环境与EFR32BG22蓝牙芯片的开发实战指南。内容涵盖开发环境安装、SDK配置、项目创建、调试烧录、外设驱动集成及常见问题解决方案,帮助开发者快速上手物联网蓝牙应用开发,并充分利用EFR32BG22的低功耗与高集成度特性。

v5w6x的博客 319

creo二次开发使用映射

在creo进行二次开发时,一些无法直接通过访问底层代码来对特征进行操作,比如“镜像装配”,这个时候就可以使用映射(Mapkey)来完成一键镜像。 注:这是镜像装配的代码 ProError status; ProMacro macro; wstring Macro; strMacro = L"mapkey(continued) ~ Command ProCmdMirrorAssem ; mapkey(continued) ~ Activate mirror_component_dialog Sy

qq_49328512的博客 1607

多线程场景使用 ArrayList

在多线程环境中使用ArrayList需要特别注意线程安全问题。Java 提供了多种解决方案来确保ArrayList在多线程环境中的安全使用,包括、手动同步以及使用并发集合类。每种方法都有其适用场景和优缺点,开发者需要根据具体的应用需求选择合适的策略,以确保程序的正确性和高效性。在设计并发程序时,理解并合理应用这些工具和方法是编写高质量、多线程安全代码的关键。

Flying_Fish_roe的博客 2097

从零到英雄:如何用FPGA和AD9288打造你的第一台便携式示波器

本文详细介绍了如何利用FPGA和AD9288 ADC芯片从零开始打造一台高性能便携式示波器。通过硬件架构设计、高速数据采集系统实现及信号处理与显示系统的开发,读者可以掌握400Msps采样率的实现方法,并了解关键器件选型与系统优化技巧。

jjj34的博客 277

为什么arrayList线程不安全-

ArrayList是Java中的一种动态数组,它在内部使用数组来存储元素。ArrayList的线程不安全性主要体现在多线程并发访问和修改同一个ArrayList实例时可能出现的问题。当多个线程同时对ArrayList进行修改操作时,可能会导致数据不一致或者出现异常。这是因为ArrayList的内部结构不是线程安全的,它没有提供对并发修改的支持。例如,当一个线程正在向ArrayList中添加元素,而另一个线程同时在删除元素,就有可能导致索引越界或者元素丢失的问题。

dzqxwzoe的博客 719

YOLO创新点改进点的消融实验设计规范

YOLO改进的消融实验是一种重要的方法,用于评估和验证模型中不同组件的影响。在进行YOLO改进的消融实验时,我们可以选择一些可能的改进点,比如Backbone网络、Anchor框设计和损失函数设计,然后通过实验来分析它们对模型性能的影响。首先,我们需要确定实验设计。对于每个改进点,我们将设计多个实验组,并针对每个实验组训练模型并进行性能评估。这里以三个改进点为例进行说明:改进点1:Backbone网络 (改进点2:Anchor框设计 (改进点3:损失函数设计 (

crasher123的博客 2909

第十六章:载板制造工艺大全

本文详细介绍了IC载板制造的完整工艺流程,主要包括开料、钻孔、孔金属化、图形形成、层压、表面处理、成型和电测8大工序。每个工序都列出了关键参数和质量控制要点,如开料尺寸精度±0.1mm、激光钻孔孔径50-150μm、孔金属化铜厚20-30μm、图形形成线宽15μm±2μm等。文中还特别说明了MSAP工艺和沉金等特殊工艺的详细流程,以及成型精度和电测标准等关键指标。全文通过流程图和参数表格直观展示了IC载板制造的核心工艺和质量要求。

Open 183

看山聊并发:如果非要在多线程中使用ArrayList会发生什么?

你好,我是看山。 我们都知道,Java中的ArrayList是非线程安全的,这个知识点太熟了,甚至面试的时候都很少问了。 但是我们真的清楚原理吗?或者知道多线程情况下使用ArrayList会发生什么? 前段时间,我们就踩坑了,而且直接踩了两个坑,今天就来扒一扒。 翠花,上源码 上代码之前先说下ArrayList的add逻辑: 检查队列中数组是否还没有添加过元素 如果是,设置当前需要长度为10,如果否,设置当前需要长度为当前队列长度+1 判断需要长度是否大于数组大小 如果是,需要扩容,将数组长度扩容1..

沉潜飞动 2066

【GameFramework框架内置模块】17、声音(Sound)

声音(Sound)模块是管理音频的模块,比如播放、停止、修改音量等,支持淡入淡出、AudioMixer等高级设置。声音(Sound)的配置面板如下所示:所有的声音统一按照组来分,可以根据需要添加或者溢出组,声音组可以配置Agent的数量和音量等。

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 1911

看山聊并发:如果非要在多线程中使用 ArrayList 会发生什么?(第二篇)

你好,我是看山。 前面写过一篇文章 《如果非要在多线程中使用 ArrayList 会发生什么?》,有读者反馈,Java 11 代码已经修复,还会出现 null 元素。 为了便于理解,当时只是通过代码执行顺序说明了异常原因。其实多线程中还会涉及 Java 内存模型,本文就从这方面说明一下。 对比源码 我们先来看看 Java 11 中,add方法做了什么调整。 Java 8 中add方法的实现: public boolean add(E e) { ensureCapacityInternal(siz.

沉潜飞动 2136

使用 ABAP Development Tool 登录 SAP BTP ABAP 环境时,遇到弹出框应该怎么办?

但在实际环境里,隐私模式、第三方 Cookie 禁止、跨站追踪限制、缓存代理误命中、混合内容拦截等因素,都可能让这条跳转链路被打断。等),浏览器会弹出系统原生登录框,请求用户输入账号和口令或做集成认证。只要它们在响应头里出现,浏览器就会理直气壮地弹框,这是协议层的强约定。,最终弹出原生登录框。前端与网关领域有个通用的经验规则:如果你看到了原生登录框,而期望的是带样式的。环境里的登录方式表述为基于浏览器的前端认证,也就是这类重定向式的交互。环境的具体落地,拆出几类常见诱因与排障路径,并穿插真实世界的案例。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 368

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

多线程不安全会发生什么_ArrayList 为什么线程不安全

//多线程调用add 方法时可能会发生覆盖 //往数组内添加元素不是原子性操作 具体分成两步 1、加入元素 2、移动标志位 flag = 0; array[flag++] = 1; //可以写成 array[flag] = 1; flag+=1; //有A、B两条线程,A线程执行到array[flag] = 1后还未移动标志位,此时线程B执行array[flag] = 2;将线程A添加的1覆盖 /...

weixin_39953618的博客 169

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,这几点一定要注意!

微信搜索逆锋起笔关注后回复编程pdf领取编程大佬们所推荐的 23 种编程资料!作者:雪山上的蒲公英www.cnblogs.com/zjfjava/p/10217720.htmlArrayL...

code小生 255

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

代码审查:从 ArrayList 说线程安全

本文从代码审查过程中发现的一个 ArrayList 相关的「线程安全」问题出发,来剖析和理解线程安全。 案例分析 前两天在代码 Review 的过程中,看到有小伙伴用了类似以下的写法: List<String> resultList = new ArrayList<>(); paramList.parallelStream().forEach(v -> { String value = doSomething(v); resultList.add(value)

靠谱-ing 485

ArrayList为什么是线程不安全的

提到线程安全我们应该第一时间想到锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用,所以通过加锁我们就可以保证一个线程的安全性,list接口下面有两个实现,一个是arraylist,另外一个是vector。 从源码的角度来看,因为vector的方法前加了,synchronized 关键字,也就是同步的意思,sun公司希望vector是线程安全的...

从简的博客 2402

ArrayList为什么要自己实现迭代器

ArrayList的父类AbstractList已经实现了Iterator接口,而且LinkedList使用AbstractList的Iterator,而ArrayList要自己实现Iterator接口? ArrayList实现Iterator,是为了避免ArrayList在迭代过程中数组结构发生变化的而产生的问题,这个处理机制称为Fail-Fast机制,实际是一个乐观锁,实现如下。 ArrayL...

咸鱼老罗的博客 1316

ArrayList迭代器并发修改异常,java基础,copyonwritelist

list数组元素计数器modcount 在iterator初始化的时候 赋值给expect 预计 count 在迭代器的next方法中会去检查是否修改 如果被修改了就回去跑出并发修改异常 modcount 在add方法的时候回去自增

weixin_44531193的博客 360

分析为什么ArrayList、HashSet以及HashMap是线程不安全的

学习的时候都知道这三个是线程不安全的,但为什么是线程不安全的、具体在哪里不安全了却不太了解,这篇文章尝试分析一下。 ArrayList 它不安全的情况主要有这么几种(前提是多线程环境下): add元素之后,会出现有些位置的值为null的情况 数组下标越界 size大小不是预期的值 下面分别对上述现象做出解释。 出现null的情况 先来看一下add方法的源码: private void add(E e, Object[] elementData, int s) { if (s == element

qq_39579087的博客 808

集合的线程安全

ensurecapacityIntermal()的作用就是如果将当前的新元素加到列表后面,判断列表的 elementData 数组的大小是否满足。如果线程A和线程B同时进行put,刚好两条数据的hash值相同,如果线程A已经判断该位置数据为nul,此时被挂起,线程B正常执行,并且正常插入数据,随后线程A继续执行就会将线程A的数据给覆盖。1、继承HashMap,重写或者按要求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用HashMap的方法。

weixin_43902408的博客 426

Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

概要 前面,我们已经学习了ArrayList。接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解。内容包括:: 1 fail-fast简介 2 fail-fast示例 3 fail-fast解决办法 4 fail-fast原理 5 解决fail-fast的原理 转载请注明出处:http://www.cnblogs.com/skywan

荒岛码农 551

线程不安全的解释

一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B...

weixin_34200628的博客 90
上一篇: 聊聊 Feign 的实现原理
下一篇: 阿里二面准备(Java 研发),精心准备200题(含答案)成为JAVA收割机
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值