【List篇】ArrayList 的线程不安全介绍

ArrayList 为什么线程不安全?

主要原因是ArrayList是非同步的,没有同步机制,并且其底层实现是基于数组,而数组的长度是固定的。当对 ArrayList 进行增删操作时,需要改变数组的长度,这就会导致多个线程可能同时操作同一个数组,从而引发线程安全问题。
具体来说,如果多个线程同时对 ArrayList 进行写操作(add、remove 等),可能会导致以下问题:

  • 数据不一致:多个线程同时修改 ArrayList 的元素,可能会导致数据不一致的情况。例如,一个线程正在修改一个元素,而另一个线程正在读取该元素,这时就会出现数据不一致的情况。

  • 索引越界:如果多个线程同时进行添加或删除元素操作,就可能导致索引越界的情况。例如,一个线程正在删除 ArrayList 中最后一个元素,而另一个线程正在向 ArrayList 中添加元素,这时就可能导致索引越界的情况。

/*** 模拟ArrayList线程不安全*/
public class UnsafeArrayList {public static void main(String[] args) {List<Integer> list = new ArrayList<>();//线程1,添加1000个元素Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {list.add(i);}});//线程2,添加1000个元素Thread t2 = new Thread(() -> {for (int i = 1000; i < 2000; i++) {list.add(i);}});//启动线程t1.start();t2.start();try {//等待两个线程执行完毕t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("期望size=2000,实际size=" + list.size());}
}

上面的代码中,我们创建了两个线程 t1 和 t2,分别向 ArrayList 中添加了 1000 个元素。但是,由于 ArrayList 不是线程安全的,所以在多线程环境下,两个线程可能会同时访问 ArrayList,导致线程安全问题。
运行上面的代码,结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出结果可能不一定是 2000,可能会小于 2000,这是因为线程之间的操作是交错执行的,有可能一个线程正在添加元素时,另一个线程就已经读取了数组的大小并返回了结果。这样就会导致对数组的修改操作在并发情况下会导致线程竞争和不确定性的结果。

  • ConcurrentModificationException场景 : 这个异常发生的原因是在迭代集合时,有一个线程对集合进行了修改,并且另一个线程正在运行迭代器,这时就会发生ConcurrentModificationException异常。

为了模拟这个异常,我们可以使用Java的多线程功能。下面是一个简单的示例代码,可以模拟ConcurrentModificationException异常:

public class ConcurrentModificationDemo {public static void main(String[] args) {//模拟在多线程场景下,由于ArrayList线程不安全引发的ConcurrentModificationException异常场景List<Integer> list = new ArrayList<>();list.add(1000);list.add(2000);list.add(3000);//执行迭代的逻辑Runnable task = () -> {Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String fruit = iterator.next();System.out.println(fruit);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}};//线程1执行迭代逻辑Thread thread1 = new Thread(task);//线程2 插入元素Thread thread2 = new Thread(() -> list.add(4000));thread1.start();thread2.start();}
}

在这个示例代码中,我们创建了一个包含三个字符串元素的列表,并使用两个线程来模拟ConcurrentModificationException异常。
一个线程使用列表的迭代器来遍历列表,并在每个元素输出之后休眠1秒。另一个线程在1秒后向列表添加一个新元素。
当第二个线程添加一个新元素时,它会修改列表,而第一个线程仍在使用迭代器遍历列表。因此,当第一个线程尝试访问下一个元素时,就会抛出ConcurrentModificationException异常。
注意,由于多线程环境的不确定性,实际运行结果可能有所不同。
在这里插入图片描述

如何解决ArrayList的线程不安全?

1.使用Collections.synchronizedList方法将ArrayList转换成线程安全的List。该方法返回一个线程安全的List,它会在每个方法调用时同步访问

List<Integer> syncList = Collections.synchronizedList(new ArrayList<Integer>());

2.使用CopyOnWriteArrayList类来替代ArrayList。CopyOnWriteArrayList是Java并发包中提供的一个线程安全的List,它通过使用写时复制技术(Copy-On-Write)来实现线程安全,每次写操作都会创建一个新的数组,读操作不会加锁,因此性能较高。

List<Integer> copyOnWriteList = new CopyOnWriteArrayList<Integer>();

3.使用Locksynchronized关键字来保证多个线程对ArrayList的操作同步。可以通过在访问ArrayList的代码块上添加synchronized关键字,或者使用Java并发包中的Lock类来实现同步。但是需要注意,这种方式可能会出现死锁等问题,并且性能也会受到影响。

private static Lock lock = new ReentrantLock();
private static List<Integer> list = new ArrayList<>();public static void addToArrayList(Integer element) {//加锁lock.lock();try {list.add(element);} finally {//释放锁lock.unlock();}
}
  1. 使用线程安全的并发容器,如ConcurrentHashMap等。

ArrayList既然线程不安全 为什么还要使用?

虽然ArrayList是线程不安全的,但是它具有以下优点:

  • 简单易用:ArrayList是Java集合框架中最简单且最常用的类之一。
  • 高效性能:相比于线程安全的Vector类,ArrayList在单线程环境下具有更高的性能。
  • 空间利用率高:ArrayList在内存使用上比数组更加灵活,能够优化内存利用率。
  • 可扩展性强:ArrayList自带自动扩容机制,能够自动调整数组大小,支持动态添加元素。

因此,对于单线程环境下的数据存储和访问,使用ArrayList是一种非常方便和高效的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/108582.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

离散制造企业如何打造MES管理系统

在当今制造业中&#xff0c;MES生产管理系统越来越受到关注&#xff0c;但在实际应用中也遇到了一些问题。本文分析了离散制造业和流程生产行业的MES应用现状&#xff0c;指出了这两个行业在部署MES管理系统时存在差异的原因&#xff0c;并探讨了如何在离散制造业提升生产效率&…

前端Javascript模块化

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 引言 前端模块化的发展历程 1.全局函数式编程 2.命名空间模式 3.CommonJS require函数 module.exports 4.AM…

PMP-项目启动过程组的重要性

一、什么是项目启动过程组 启动过程组包括定义一个新项目或现有项目的一个新阶段&#xff0c;授权开始该项目或阶段的一组过程。启动过程组的目的是&#xff1a;协调相关方期望与项目目的&#xff0c;告知相关方项目范围和目标&#xff0c;并商讨他们对项目及相关阶段的参与将如…

JMeter-BeanShell预处理程序和BeanShell后置处理程序的应用

一、什么是BeanShell&#xff1f; BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器&#xff0c;JMeter性能测试工具也充分接纳了BeanShell解释器&#xff0c;封装成了可配置的BeanShell前置和后置处理器&#xff0c;分别是 BeanShell Pre…

远程桌面工具

PRemoteM 是一款现代的远程会话管理和启动器&#xff0c;它让你能够在任何时候快速开启一个远程会话。目前 PRemoteM 已支持 微软远程桌面(RDP)、VNC、SSH、Telnet、SFTP, FTP, RemoteApp等协议。 图片 1 PRemoteM 简介 如果你远程连接windows桌面仍旧在使用winR&#xff0c;输…

靶场上新:Openfire身份认证绕过

本文由掌控安全学院-江月投稿 封神台新上线漏洞复现靶场&#xff1a;Openfire身份认证绕过。 漏洞详情&#xff1a; Openfire是采用Java编程语言开发的实时协作服务器&#xff0c;Openfire的管理控制台是一个基于Web的应用程序&#xff0c;被发现可以使用路径遍历的方式绕过…

内网穿透——Windows搭建服务器

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

机器学习基础之《分类算法(6)—决策树》

一、决策树 1、认识决策树 决策树思想的来源非常朴素&#xff0c;程序设计中的条件分支结构就是if-else结构&#xff0c;最早的决策树就是利用这类结构分割数据的一种分类学习方法 2、一个对话的例子 想一想这个女生为什么把年龄放在最上面判断&#xff01;&#xff01;&…

四叶草clover配置工具:Clover Configurator for Mac

Clover Configurator是一款Mac上的工具&#xff0c;用于配置和优化Clover引导加载器。Clover引导加载器是一种用于启动macOS的开源引导加载器。它允许用户在启动时选择操作系统和配置启动选项。 Clover Configurator提供了一个可视化的界面&#xff0c;让用户可以轻松地编辑和…

关于时空数据的培训 GAN:实用指南(第 01/3 部分)

第 1 部分&#xff1a;深入了解 GAN 训练中最臭名昭著的不稳定性。 一、说明 GAN 是迄今为止最受欢迎的深度生成模型&#xff0c;主要是因为它们最近在图像生成任务上产生了令人难以置信的结果。然而&#xff0c;GAN并不容易训练&#xff0c;因为它们的基本设计引入了无数的不稳…

学习Bootstrap 5的第十一天

目录 折叠 基础的折叠 实例 Accordion&#xff08;手风琴&#xff09; 实例 导航 导航菜单 实例 导航对齐方式 实例 垂直导航栏 实例 动态选项卡 实例 胶囊状动态选项卡 实例 等宽的选项卡/胶囊的下拉菜单 实例 导航栏 基础的导航栏 实例 垂直导航栏 实…

多元函数的偏导数

目录 偏导数的定义 二元函数偏导数的几何意义 高阶偏导数 全微分 偏导数的定义 偏导数是一种特殊的数学概念&#xff0c;它是针对一个多变量的函数在某个自变量上的导数。 具体来说&#xff0c;对于一个有多个自变量的函数yf(x0, x1, xj, ..., xn)&#xff0c;在自变量xk固…