ArrayList 线程安全问题

ArrayList 线程安全问题前言在观看多线程书籍的时候 经常会看到大家提及 ArrayList 与 HashMap 时候 皆会说明 ArrayList 与 HashMap 类型都不是线程安全的 那么 在传统的集合包内的集合类到底为什么线程非安全呢 在新的 JUC 包类又有什么可以替代呢 让我们开始今天的部分 本章主要包括如下几个部分 为什么 ArrayList 是线程非安全的 替代措施 Vector 类 Colletions 封装

前言

在观看多线程书籍的时候,经常会看到大家提及ArrayListHashMap时候.皆会说明, ArrayListHashMap类型都不是线程安全的. 那么,在传统的集合包内的集合类到底为什么线程非安全呢?在新的JUC包类又有什么可以替代呢? 由于篇幅问题, 我们将这个问题分成两章进行分析.本章中, 我们主要分析下ArrayList的部分.

本章主要包括如下几个部分:

  • 为什么ArrayList是线程非安全的?
  • 替代措施(Vector类 / Colletions封装 / JUC类)

ArrayList 与 非线程安全

ArrayList线程不安全详解

我们先执行如下例子:

import java.util.ArrayList; import java.util.List; / * ArrayList的非线程安全演示. * * */ class UnsafeArrayListThread extends Thread{ public void run(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } UnsafeArrayList.arrayList.add(Thread.currentThread().getName()+" "+System.currentTimeMillis()); } } public class UnsafeArrayList { public static List arrayList = new ArrayList(); public static void main(String[] args) { Thread []threadArray = new Thread[1000]; for(int i=0;i 
   

在输出时,我们会遇到这样的几种情况:

  1. 输出值为null;
  2. 数组越界异常;
  3. 某些线程没有输出值;
1. 输出值为null; //null //Thread-958 //Thread-958 //Thread-958 //Thread-959 3. 某些线程没有输出值; // 某些输出缺少. 2. 数组越界异常; //Exception in thread "Thread-874" java.lang.ArrayIndexOutOfBoundsException: 823 //at java.util.ArrayList.add(ArrayList.java:441) //at com.yanxml.multithreading.art.collection.UnsafeArrayListThread.run(UnsafeArrayList.java:17) 

这些都是在多线程中使用ArrayList类所会遇到的问题.下面我们分别根据上面进行一一解答:

我们看下ArrayList的源码

 public boolean add(E e) { // 确保ArrayList的长度足够 ensureCapacityInternal(size + 1); // Increments modCount!! // ArrayList加入 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } // 如果超过界限 数组长度增长 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } 

在上述过程中,会出问题的部分在于: 1. 增加元素 2. 扩充数组长度;

增加元素过程中较为容易出现问题的部分在于elementData[size++] = e;.赋值的过程可以分为两个步骤elementData[size] = e;size++;

我们分别使用两个线程来模拟插入过程.例如有两个线程,分别加入数字1与2.

在这里插入图片描述
运行的过程如下所示:

  1. 线程1 赋值 element[1] = 1; 随后因为时间片用完而中断;
  2. 线程2 赋值 element[1] = 2; 随后因为时间片用完中断;
  • 此处导致了之前所说的一个问题(有的线程没有输出); 因为后续的线程将前面的线程的值覆盖了.
  1. 线程1 自增 size++; (size=2)
  2. 线程2 自增 size++; (size=3)
  • 此处导致了某些值为null的问题.因为原size=1, 但是因为线程1与线程2都将值赋值给了element[1],导致了element[2]内没有值,被跳过了.指针index指向了3.所以,导致了某些情况下值为null的情况.
  1. 线程1 判断数组是否越界.因为size=2 长度为2,没有越界.将进行赋值操作.但是因为时间片问题导致了中断.
  2. 线程2 判断数组是否越界.因为size=2 长度为2,没有越界.将进行赋值操作.但是因为时间片问题导致了中断.
  3. 线程1 重新获取到主动权.上文判断了长度刚刚好够用.进行赋值操作element[size]=1,并且size++
  4. 线程2 因为上文判断了数组没有越界.所以进行赋值操作.但是此时的size=3了.再执行element[3]=2. 导致了数组越界了.
  • 由此处可以看出因为数组的当前指向size并未进行加锁的操作,导致了数组越界的情况出现.

所以, ArrayList类是非线程安全的类!


解决措施

  • 使用Vector类进行替换.
    将上文的public static List arrayList = new ArrayList();替换为public static List arrayList = new Vector<>();

原理:使用了synchronized关键字进行了加锁处理.

public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } 
  • 使用Collections.synchronizedList(List)进行替换
    public static List arrayList = Collections.synchronizedList(new ArrayList());
    原理: 使用mutex对象进行维护处理.Object mutex = new Object(). 这边就是创建了一个临时空间用于辅助独占的处理.




# 转化方法如下 public static 
    
      List 
     
       synchronizedList(List 
      
        list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } # SynchronizedList类如下 static class SynchronizedList 
       
         extends SynchronizedCollection 
        
          implements List 
         
           { // 其中的add方法如下 public void add(int index, E element) { synchronized (mutex) {list.add(index, element);} } } 
          
         
        
       
      
    
  • 使用JUC中的CopyOnWriteArrayList类进行替换.(具体见JUC的集合框架篇)

后记


Reference

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208404.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午11:40
下一篇 2026年3月19日 上午11:41


相关推荐

  • 浅析Java中volatile关键字及其作用

    浅析Java中volatile关键字及其作用在Java多线程中如何保证线程的安全性?那我们可以使用Synchronized同步锁来给需要多个线程访问的代码块加锁以保证线程安全性。

    2022年5月31日
    40
  • windows 10下无法安装.NET Framework 3.5

    windows 10下无法安装.NET Framework 3.5解决方案:win键+R,输入services.msc,“确定”打开服务,在右侧列表里找到“WindowsUpdate”双击打开后点击“启动”(若按钮灰色则把“启动类型”中的“禁用”改为“自动”)即可。(.NETFramework安装完成后如果你想继续关闭windowsupdate就继续把前面说的服务“停止”后“禁用”。)转载于:https://www.cnblogs…

    2022年6月1日
    54
  • ChatGPT Plus 充值教程 国内支付宝GPT充值教程(保教会版) – 智技AI

    ChatGPT Plus 充值教程 国内支付宝GPT充值教程(保教会版) – 智技AI

    2026年3月16日
    2
  • ofbiz 开发

    ofbiz 开发1 Ofbiz 介绍 Ofbiz http www ofbiz org 是 OpenSource 的商务软件系统 充分利用了各优秀的的 OpenSource 项目 像 Tomcat Ant BeanShell Jboss 等 构建了一个强大的系统平台 Ofbiz 已经完成了大部分商务类软件系统都需要的部件 像用户认证 工作流 商务规则处理等 Ofbiz 的核心技术在

    2026年3月19日
    2
  • dpkg 命令详解[通俗易懂]

    dpkg 命令详解[通俗易懂]名词解释    “dpkg”是“DebianPackager”的简写。为“Debian”专门开发的套件管理系统,方便软件的安装、更新及移除。所有源自“Debian”的“Linux”发行版都会使用“dpkg”,例如“Ubuntu”、“Knoppix”等。名词由来    dpkg是Debian软件包管理器的基础,它由伊恩·默多克于1993年创

    2022年5月21日
    88
  • rownum的特点以及它与order by 子句的执行顺序关系

    rownum的特点以及它与order by 子句的执行顺序关系rownum 的特点是在 select 语句查询过程中获得一条符合条件的数据行放入结果集合中时 oracle 系统就会自动编一个号 即一个 rownum 值 这里要强调一点的是 一个结果集合里的任何时刻 都是有一个 rownum 值为 1 的数据行的 其后的数据行编号依次为 2 3 以此类推的 如果那一条 rownum 值为 1 的数据行从结果集合里剔除了 跟在其后为 2 的数据行自动变为 1 其他行也跟着做相应变化

    2026年3月17日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号