为什么 ArrayList是线程不安全却在开发中被广泛使用?

【List篇】第三篇 ArrayList 的线程不安全介绍 ArrayList 不是线程安全的 阅读详情

在 Java 开发中,ArrayList 是最常用的集合类之一。尽管我们知道它不是线程安全的,但在实际项目中却频繁出现。本文将从技术原理、业务场景和代码实现三个层面,详细分析这一现象背后的原因。

一、ArrayList 线程不安全的技术原理

(一)线程安全问题的核心根源:缺乏同步机制

ArrayList 的底层实现是动态数组,其元素添加、删除等操作的核心方法(如add()、remove())均未使用synchronized关键字修饰,这意味着多个线程同时操作同一 ArrayList 实例时,可能出现数据不一致问题。

示例代码:多线程环境下的元素添加

public class ArrayListThreadIssueDemo {
    private static List<String> list = new ArrayList<>();
    public static void main(String[] args) {
        // 创建100个线程,每个线程向list中添加1000个元素
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    list.add("element"); // 多个线程同时调用add方法,无同步保护
                }
            }).start();
        }
        // 等待所有线程执行完毕(此处简化处理,实际需使用CountDownLatch等工具)
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("预期大小:100000,实际大小:" + list.size());
        // 输出结果可能小于100000,因为多线程操作导致元素丢失或数据不一致
    }
}

(二)扩容机制引发的线程安全问题

当 ArrayList 的元素数量超过当前容量时,会触发扩容操作。扩容过程包括计算新容量、创建新数组、复制原数组元素到新数组,最后更新底层数组引用。这一过程在多线程环境下可能引发问题。

扩容核心代码分析

// ArrayList类中的扩容方法
private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 获取当前数组容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为原容量的1.5倍
    // 复制原数组元素到新数组,并更新底层数组引用
    elementData = Arrays.copyOf(elementData, newCapacity);
}

多线程风险:若两个线程同时触发扩容,可能导致其中一个线程的扩容结果被覆盖,最终底层数组elementData可能只保留最后一次扩容的结果,中间线程的元素添加操作可能丢失。

(三)迭代器与 modCount 机制的冲突

ArrayList 通过modCount变量记录集合的修改次数,迭代器在遍历时会检查该变量是否被修改。多线程环境下,若一个线程在迭代过程中,另一个线程修改了集合,迭代器会抛出ConcurrentModificationException。

迭代器检查代码

// ArrayList的迭代器实现(简化版)
private class Itr implements Iterator<E> {
    int expectedModCount = modCount; // 记录迭代开始时的修改次数
    public boolean hasNext() {
        return cursor != size;
    }
    public E next() {
        checkForComodification(); // 检查修改次数是否一致
        // ... 其他逻辑
    }
    final void checkForComodification() {
        if (modCount != expectedModCount) // 若修改次数变化,抛出异常
            throw new ConcurrentModificationException();
    }
}

二、实际开发中广泛使用 ArrayList 的原因

(一)单线程场景占绝大多数

在大多数业务系统中,集合的使用场景属于单线程环境:

  • Web 请求处理:Controller 层接收请求后,在单个线程内处理数据,组装业务对象(如将数据库查询结果存入 ArrayList)。
  • 方法内部逻辑:在某个方法内部创建 ArrayList,完成数据处理后即被销毁,不存在多线程共享问题。

这种情况下,ArrayList 的线程不安全特性完全不会暴露,就像在单车道上行驶的车辆,无需担心对向车道的碰撞风险。

(二)性能优势显著优于线程安全集合

与线程安全的Vector相比,ArrayList 在单线程环境下的性能优势明显:

  1. 无锁设计:ArrayList 的方法未添加synchronized,避免了锁竞争带来的性能损耗。
  1. 高效遍历:使用普通 for 循环遍历 ArrayList 的效率远高于 Vector 的Enumeration迭代器。

性能对比(简化数据)

操作

ArrayList(单线程)

Vector(线程安全)

元素添加

O (1)(平均)

O (1)(有锁开销)

普通 for 遍历

100ms

130ms

(三)成熟的线程安全解决方案

当确实需要多线程操作集合时,Java 提供了多种安全方案,无需强制使用线程安全的 Vector:

  1. 同步包装器:通过Collections.synchronizedList()将 ArrayList 包装为线程安全集合。
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// 使用时需手动同步
synchronized (safeList) {
    safeList.add("element");
}
  1. 写时复制容器:CopyOnWriteArrayList适用于读多写少场景,写操作时复制数组保证线程安全,读操作无锁。
List<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("write"); // 写操作复制数组,性能开销在写时
for (String s : cowList) { // 读操作直接访问原数组,高效安全
    process(s);
}
  1. 手动控制锁范围:通过自定义锁对象,缩小同步代码块范围,平衡安全与性能。
private static List<String> list = new ArrayList<>();
private static final Object LOCK = new Object(); // 自定义锁对象
public static void safeAdd(String element) {
    synchronized (LOCK) { // 仅在添加元素时加锁
        list.add(element);
    }
}

三、核心代码中的安全与效率平衡

(一)复现线程安全问题的完整代码(带详细注释)

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
public class ArrayListThreadUnsafeDemo {
    private static List<String> list = new ArrayList<>(); // 共享的ArrayList实例
    private static final int THREAD_COUNT = 100; // 线程数量
    private static final int ELEMENT_PER_THREAD = 1000; // 每个线程添加的元素数量
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT); // 用于等待所有线程完成
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < ELEMENT_PER_THREAD; j++) {
                        // 生成唯一字符串并添加到list,模拟多线程并发写
                        list.add(UUID.randomUUID().toString());
                    }
                } finally {
                    latch.countDown(); // 线程完成时减少计数器
                }
            }).start();
        }
        latch.await(); // 等待所有线程完成
        System.out.println("预期元素总数:" + (THREAD_COUNT * ELEMENT_PER_THREAD));
        System.out.println("实际元素总数:" + list.size());
        // 输出结果通常小于100000,证明多线程下元素可能丢失
    }
}

(二)安全使用 ArrayList 的最佳实践代码

1. 使用同步包装器(适合中等并发场景)

import java.util.Collections;
import java.util.List;
public class SynchronizedListDemo {
    private static List<String> safeList = 
        Collections.synchronizedList(new ArrayList<>()); // 包装为线程安全列表
    public static void addElement(String element) {
        synchronized (safeList) { // 手动对列表加锁,保证操作原子性
            safeList.add(element);
        }
    }
}

2. 使用 CopyOnWriteArrayList(适合读多写少场景)

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteDemo {
    private static List<String> cowList = new CopyOnWriteArrayList<>(); // 写时复制列表
    public static void main(String[] args) {
        // 写操作:创建新数组并复制原数据,保证线程安全
        cowList.add("write once");
        // 读操作:直接访问底层数组,无需加锁,效率高
        for (String s : cowList) {
            System.out.println(s);
        }
    }
}

四、总结:理性选择工具的核心逻辑

ArrayList 的广泛使用,本质上是技术选型中场景适配与性价比的结果:

  1. 场景适配:大多数业务逻辑运行在单线程环境,无需为极低概率的多线程问题付出额外成本。
  1. 性能优先:在单线程场景下,ArrayList 的无锁设计带来显著的性能优势,远超 Vector 等线程安全集合。
  1. 方案灵活:当遇到多线程场景时,Java 生态提供了成熟的解决方案(如同步包装器、写时复制容器),允许开发者按需选择。

理解 ArrayList 的线程安全问题,不是为了避免使用它,而是为了在合适的场景中合理运用,并在需要时采用正确的防护措施。这体现了软件开发中一个重要原则:没有绝对完美的工具,只有针对具体场景的最优解。

ArrayList vs Vector:一场线程安全与性能优化的世纪之争! 1、ArrayList 和 Vector 是什么?ArrayList是 Java 集合框架中的一个类,位于 java.util 包中,是一个实现了可变大小数组的列表。它是非线程安全的,性能优于 Vector。Vector同样是一个可变大小的数组列表,最早引入于 JDK 1.0。它是线程安全的,因为它的方法大多被 synchronized 修饰。2、历史沿革Vector 是 Java 1.0 时的产物,那个时候还没有 Java 集合框架。 阅读详情

相关推荐

Java中对比Vector, ArrayList, LinkedList的区别

创作不易,请大家多多支持。我们在日常的工作中,能够高效地管理和操作数据是非常重要的。由于每个编程语言支持的数据结构不尽相同,比如我最早学习的 C语言,需要自己实现很多基础数据结构,管理和操作会比较麻烦。相比之下,Java 则要方便的多,针对通用场景的需求,Java提供了强大的集合框架,大大提高了开发者的生产力。今天我要问你的是有关集合框架方面的问题,对比Vector、ArrayList、LinkedList 有何区别?

A542307230632的博客 2071

ArrayList 如何保证线程安全

四种保证ArrayList线程安全的方法

liuhaikang的博客 7351

面试官问 : ArrayList 不是线程安全的,为什么 ?(看完这篇,以后反问面试官)

面试官问 : ArrayList 不是线程安全的,为什么 ????

默默不代表沉默 9333

ArrayList线程安全问题以及解决方案

1. 多线程下的ArrayList public class ConcurrentArrayList { public static void main(String[] args) throws InterruptedException { List<Integer> list = new ArrayList<>(); Runnable runnable = () -> { for (int i = 0; i

xsjzn的博客 1万+

Java进阶:ArrayList线程安全问题和解决方案

概述 ArrayList 是线程不安全的集合类,当多线程环境下,并发对同一个ArrayList执行add,可能会抛出java.util.ConcurrentModificationException的异常 例子 这边有个简单的程序,创建30个线程,分别对ArrayList执行add操作 public class ListApp { public static void main( String[] args ) throws InterruptedException { List&l

qq_28834355的博客 2309

为什么ArrayList线程不安全?不安全为什么要使用?如何解决线程不安全?

https://blog.youkuaiyun.com/qq_28081081/article/details/80413669 1...

483

为什么 ArrayList 是线程不安全却在开发中被广泛使用?

通过手动控制锁的粒度,平衡安全性和性能。

m0_60456028的博客 681

蓝牙Channel Sounding为真正的距离感知赋能

1:工作原理蓝牙™信道探测使用两种经过验证的测距方法——PBR(PBR)和RTT(RTT),实现蓝牙设备之间的安全精密测距。

weixin_43720159的博客 807

为什么 ArrayList 是线程不安全的,却依然被广泛使用?——深入解析与工程实践指南

本文从底层原理、源码行为到多线程案例入手,深入分析了 ArrayList 为什么线程不安全,并结合扩容机制、modCount Fail-Fast 等细节展示了真实风险。同时从单线程场景、性能优势、工程选型等角度解释了 为什么 ArrayList 仍然是最常用的 List 类型。文章最后给出了线程安全集合的对比与选型建议,帮助开发者在不同业务场景中选择最合适的集合结构。

往者之不谏 来者犹可追 1283

从ST转国产ARM必看:GD32/APM32等5款芯片的SWD下载全攻略(含CH32特殊处理)

本文为从STM32转向国产ARM芯片的开发者提供了详细的SWD下载调试指南。针对GD32F103C8T6、APM32F103C8T6等五款热门国产芯片,详细说明了如何利用已有的ST-Link工具和Keil MDK环境进行程序烧录,并特别介绍了CH32F103芯片所需的独立器件库安装等特殊处理方法,帮助开发者实现平滑迁移,提升开发效率。

weixin_29195615的博客 278

Cadence Virtuoso IC617:从零构建传输门与反相器的Schematic设计实战

本文详细介绍了使用Cadence Virtuoso IC617进行传输门与反相器Schematic设计的完整流程,包括环境准备、原理图绘制、Symbol创建、仿真验证及实用技巧。通过分步指导和实战经验分享,帮助初学者快速掌握集成电路设计基础,提升EDA工具应用能力。

weixin_29194693的博客 600

ArrayList线程不安全却被广泛使用的深层逻辑:从原理到实践的全面解析

ArrayList的“线程不安全却广泛使用”,不是开发者的“忽视”,而是对技术本质的深刻理解——没有完美的工具,只有适配的选择。在单线程为主的业务场景中,它的高效性和易用性使其成为最优解;而在多线程场景中,开发者可通过Java生态提供的丰富工具规避风险。理解其不安全的底层原因;识别当前场景是否存在多线程共享风险;能根据场景选择合适的线程安全方案。这正是Java集合框架设计的精妙之处——它不替开发者做决定,而是提供足够的组件和灵活性,让开发者在安全与性能之间找到平衡。

excellent_fish_c的博客 1094

【3DSlicer】基于心脏ct影像重建3d模型及导出

软件:3DSlicer4.11 基于心脏ct影像重建3d模型

Liuees的博客 1万+

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

1,什么是线程安全与不安全? 在多线程访问的时候,采用了加锁的机制,当一个线程访问一个类的数据时对其进行保护,在此期间,其他线程不能访问,直到该线程操作完,其他线程才可以进行操作,这样的话不会产生数据不一致或对数据产生污染等问题。如果线程不安全,多个线程操作同一个类可能会产生数据不一致的问题。 2,举例写出ArrayList是线程不安全的 查看add方法: 从源码中看到,add方法中并没有sync...

weixin_44774355的博客 345

DFI协议时序

参数名称典型值应用场景关键影响tcmd_lat1-2个时钟周期命令与地址锁存同步地址错误风险tphy_wrlat1-2个时钟周期写操作响应速度写带宽与延迟2-3个时钟周期写操作端到端延迟系统吞吐量1-2个时钟周期连续写命令间隔信号干扰与资源冲突tphy_rdlat1-2个时钟周期读操作响应速度读带宽与延迟trddata_en由CL决定(如0.41 ns)读数据返回时间系统延迟10-20 ps写数据与命令对齐数据丢失风险<1 µs低功耗模式切换。

qq_43134430的博客 5103

Deepfake检测模型-PyDeepFakeDet项目复现实验报告-深度学习

该实验是对于https://github.com/wangjk666/PyDeepFakeDet模型的复现。PyDeepFakeDet 是一款集成的、可扩展的 Deepfake 检测工具。目标是提供最先进的 Deepfake 检测模型以及用于在常用 Deepfake 数据集上训练和评估新模型的接口。

一位不是很硕的鉴伪方向硕士 5495

ArrayList底层原理、线程安全及其相关集合(面试常问)

在面试中经常会问到一些ArrayList的底层原理、线程安全以及ArrayList相关集合等问题,现做一个总结方便自己日后复习也供各位小伙伴参考学习,如有什么问题欢迎指正。

weixin_59547613的博客 2959

SAP BOM批量反查代码分享

SAP BOM反查批量查询代码分享

我小时候很黑的博客 4972

java 线程安全和不安全_java 线程安全和不安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。(Vector,HashTab;le)线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。(ArrayList,LinkedList,HashMap等)概念:如果你的代码所在的进程中有多个线...

weixin_29486701的博客 314

基于 PID 算法的 STM32 步进电机高精度位置控制系统设计与实现(工业自动化应用)

硬件层面:选用 STM32F103C8T6+A4988 + 欧姆龙编码器的组合,兼顾工业可靠性与成本,接线需注意强弱电分离和信号抗干扰;软件层面:核心为位置式 PID 闭环控制,Mermaid 流程图清晰展现 “采集 - 运算 - 驱动” 的闭环逻辑,代码模块化易移植;性能层面:无负载定位误差≤±1 脉冲,带载误差≤±3 脉冲,满足工业自动化高精度位置控制需求。

weixin_42300449的博客 194

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

线程安全和线程不安全理解

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据 概念: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行...

K_kzj_K的博客 427

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...

【Java】如何保证ArrayList的线程安全?

ArrayList使用十分广泛,但它是线程不安全的,但实际使用中,我们的多线程实现,普遍都是基于一些同步方法或者锁,很多场景其实并不需要关注ArrayList本身的线程安全。

qq_44544908的博客 2135

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: 高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
下一篇: Nginx 配置实战:从摸鱼到部署,手把手教你搞定生产级配置
程序员职业指南
博客等级 码龄4年 2136粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值