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

ArrayList 为什么线程不安全?

主要原因是ArrayList是非同步的,没有同步机制,并且其底层实现是基于数组,而数组的长度是固定的。当对 ArrayList 进行增删操作时,需要改变数组的长度,这就会导致多个线程可能同时操作同一个数组,从而引发线程安全问题。

具体来说,如果多个线程同时对 ArrayList 进行写操作(add、remove 等),可能会导致以下问题:

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

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

coffeescript 复制代码
/**
 * 模拟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异常:

csharp 复制代码
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,它会在每个方法调用时同步访问。

coffeescript 复制代码
List<Integer> syncList = Collections.synchronizedList(new ArrayList<Integer>());

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

coffeescript 复制代码
List<Integer> copyOnWriteList = new CopyOnWriteArrayList<Integer>();

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

coffeescript 复制代码
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是一种非常方便和高效的选择。

相关推荐
weixin_460443562 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
海鸥-w5 小时前
什么是AOP
java
EasyGBS6 小时前
摄像头还在线,画面已经不能用了?国标GB28181视频平台EasyGBS视频质量诊断怎么用
服务器·安全·音视频
Cc.Y6 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
paopaokaka_luck6 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787056 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev6 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha6 小时前
Lambda 表达式和函数式接口
java·开发语言
计算机毕业设计杰瑞6 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
迪康Defender6 小时前
终端安全审批体系设计:如何平衡安全管控与业务连续性
运维·服务器·安全·运维开发·学习方法·业界资讯·远程工作