ArrayList为什么线程不安全以及三种解决办法【详细】

news2024/11/13 18:40:35

目录

  • 不安全原因
  • 解决办法
    • Vector
    • Collections
    • CopyOnWriteArrayList
  • 三种解决方式总结

不安全原因

  • 我们可以看一下ArrayList源码,找到add方法,
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

从上面的代码可以看出,add()方法没有使用同步互斥,所以在多线程并发中,会出现线程异常,测试代码:

import java.util.ArrayList;
import java.util.UUID;

public class SetUnsefertyTest {
    public static void main(String[] args) {
        // 创建ArrayList 集合
        ArrayList<String> list = new ArrayList<>();

        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

出现异常:
请添加图片描述

解决办法

Vector

可以看一下Vector的add源码,加上了synchronized同步关键字
但是 Vector 用的不多,因为每次对添加的元素上锁,而且使用的是重量级锁synchronized是十分占用资源的,效率是十分低下的。其用法和 ArrayList 一样。

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

测试代码:

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

public class SetUnsefertyTest {
    public static void main(String[] args) {
        // 创建ArrayList 集合
        List<String> list = new Vector<String>();

        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

Collections

进入 Collections 的底层,找到 synchronizedList(List list) 方法,源代码如下,synchronizedList(List list) 方法返回指定列表支持的同步(线程安全的)列表

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

static <T> List<T> synchronizedList(List<T> list, Object mutex) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list, mutex) :
            new SynchronizedList<>(list, mutex));
}

对 Collections 的使用如下

List<String> list = Collections.synchronizedList(new ArrayList<>());

测试代码:

import java.util.*;

public class SetUnsefertyTest {
    public static void main(String[] args) {
        // 创建ArrayList 集合
//        List<String> list = new Vector<String>();

        List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList

这是写时复制思想, 首先看add()方法中有可重入锁,这个目的是防止多个线程争抢写的权力,然后下面红框中的内容是将原件复制出来一份,然后在复印件上写,之后通过setArray()方法让原件地址指向复印件,这样可以让所有人读原件,而我只修改复印件,所以读和写不会出现冲突,因此通过加锁和写时复制思想可以很好保证了多线程情况下所有线程都可以读,但是只有一个线程在写,因此不会出现并发修改异常,如源码:

public boolean add(E e) {
    // 声明一个重进入锁
    final ReentrantLock lock = this.lock;
    // 上锁
    lock.lock();
    try {
        // 获取原来的列表
        Object[] elements = getArray();
        // 获取原来列表的长度
        int len = elements.length;
        // 复制一个与原来的列表一样的列表
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 将新加入的元素放到列表末尾
        newElements[len] = e;
        // 旧新合并
        setArray(newElements);
        return true;
    } finally {
        // 解锁
        lock.unlock();
    }
}

测试代码:

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class SetUnsefertyTest {
    public static void main(String[] args) {
        // 创建ArrayList 集合
//        List<String> list = new Vector<String>();

//        List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
        List<String> list =new CopyOnWriteArrayList<>() ;
        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

三种解决方式总结

对比三者来看,Vector和Collections虽然也可以实现同步,但由于这两种方法在底层都使用了synchronized重量级锁,使其效率很低,所以对 ArrayList 的同步主要采用 CopyOnWriteArrayList

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【java】Lambda表达式

文章目录体验Lambda表达式Lambda表达式的标准格式Lambda表达式的练习抽象方法无参无返回值抽象方法带参无返回值抽象方法带参带返回值Lambda表达式的省略模式Lambda表达式的注意事项Lambda表达式和匿名内部类的区别体验Lambda表达式 package heima.Lambda;import heima.多线程.…

HTML5期末大作业:旅游网页设计与实现——旅游风景区网站HTML+CSS+JavaScript 景点静态网页设计 学生DW静态网页设计

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

JMeter入门教程(9) --参数化

文章目录1.任务背景2.任务目标3.任务实操3.1 CSV数据文件1.任务背景 参数化是测试过程中很常用的一种技巧&#xff0c;可以将脚本中的某些输入用参数来代替&#xff0c;比如登陆时传递参数&#xff0c;在脚本运行时指定参数的取值范围和规则 2.任务目标 掌握基于JMeter性能测…

【浅学Java】SpringBoot创建和使用

SpringBoot创建和使用1. SpringBoot是什么2. SpringBoot的优点3. SpringBoot的创建3.1 使用idea创建3.2 测试项目_输出hello3.3 网页版创建SpringBoot4. 约定大于配置1. SpringBoot是什么 Spring的诞生是为了简化Java开发而创建的&#xff0c;而SpringBoot的诞生就是为了简化S…

一文带你理解【自然语言处理(NLP)】的基本概念及应用

觉得有帮助请点赞关注收藏~~~ 1.1 自然语言处理 1.1.1 自然语言处理主要研究对象 自然语言处理&#xff08;Natural Language Processing&#xff1a;NLP&#xff09;是以人类社会的语言信息&#xff08;比如语音和文本&#xff09;为主要研究对象&#xff0c;利用计算机技术来…

C++:深拷贝和浅拷贝——拷贝构造、赋值构造必须自定义

https://www.bilibili.com/video/BV1qT4y1X7cQ/?spm_id_from333.337.search-card.all.click&vd_sourced33b44674c517c8b7928e8d3ac316b37 1、赋值运算符重载 浅拷贝的错误代码&#xff1a; class Distance { public:int* dis NULL;Distance(int a){dis new int(a);}~…

轻量级xshell+manager远程监控jvisualvm

一、服务器端&#xff08;Linux&#xff0c;最小安装模式&#xff0c;没有图形界面&#xff09; 1.安装xauth 如果在Xshell中配置了X11转发后&#xff0c;出现如下提示&#xff1a; WARNING! The remote SSH server rejected X11 forwarding request. 则需要查看/etc/ssh/s…

esxi 6.7下安装openwrt(iStoreOS)网卡直通

esxi 6.7下安装openwrt&#xff08;iStoreOS&#xff09;网卡直通 esxi上创建一个iStoreOS系统的虚拟机&#xff0c;当主路由使用&#xff0c;网卡直通方式 1、工具 硬件&#xff1a; 工控机&#xff1a;装有esxi6.7系统&#xff08;192.168.100.2&#xff09;&#xff0c;配…

图像仿射变换与双线性插值

图像变换 下面的所有变换假设都是针对一幅图像&#xff0c;即一个三维数组&#xff08;HWC&#xff09;&#xff0c;这里为简单起见&#xff0c;假设图像都是单通道&#xff08;C1&#xff09;的。 (x,y)(x,y)(x,y): 原图像中某一点 A 的位置(x′,y′)(x′,y′)(x′,y′): 变换…

记一次系统重装后电脑的优化设置

总目录 文章目录总目录前言一、Windows设置1.系统2.应用3.隐私4.更新与安全二、系统使用偏好设置1.设置远程协助2.文件资源管理器偏好设置3.用户账户控制设置4.修改桌面文件的路径5.根据需求删除系统自带的无用的软件总结前言 由于之前总是电脑用着用着C盘不是满了&#xff0c…

Spring框架(九):Spring注解开发Annotation

Spring注解开发引子如何用注解替代xml基础配置Bean可以加一些注解来实现原有的xml文件的功能Component注解及其衍生注解依赖注入AutowireSpring非自定义的注解开发Spring其他注解注解的原理解析-xml方式注解的原理解析-注解方式注解整合MyBatis框架注解整合第三方框架引子 痛定…

一篇文章让你搞懂Java顺序表

目录 一、 线性表的基本介绍 二、顺序表 1、顺序表的概念 2. 创建顺序表类&#xff08;ArrayList&#xff09; 2. 增加元素 3. 删除元素 4. 修改某个元素 5. 查找元素 Main类 在数据结构体系中我们将整个数据结构分为两类&#xff0c;一类是线性结构&#xff1b; 线性…

微电网两阶段鲁棒优化经济调度方法(完美复现)

针对微电网内可再生能源和负荷的不确定性&#xff0c;建立了 min-max-min 结构的两阶段鲁棒优化模型&#xff0c;可得到最恶劣场 景下运行成本最低的调度方案。模型中考虑了储能、需求侧 负荷及可控分布式电源等的运行约束和协调控制&#xff0c;并引入了 不确定性调节参数&…

Java基于springboot+vue+element医疗用品销售购物商城系统 前后端分离

开发背景和意义 网络购物己经成为一个常态化的消费手段&#xff0c;足不出户即可享受互联网发展的红利&#xff0c;对于购物商城的应用&#xff0c;普通消费者目前普遍使用。医疗用品作为一个大众消费的商品&#xff0c;由于其健康的特点&#xff0c;也越来越为大家喜欢&#…

TypeScript 知识点总结

对于有着强制类型语言经验的开发来讲&#xff0c;刚开始接触 JavaScript 的时候&#xff0c;大多都有一种 “心如芒刺&#xff0c;如鲠在喉” 的感觉。 从最初的好感 -——开放包容&#xff0c;到后来的厌恶之情——放荡不羁 TypeScript 犹如黑暗之中的一缕阳光&#xff0c;拯救…

【单片机基础】ADC0832详解

文章目录一、ADC0832介绍1、功能特点2、引脚说明3、ADC0832与单片机接口4、工作时序二、例程一、ADC0832介绍 ADC0832 是美国国家半导体公司生产的一种8 位分辨率、双通道A/D转换芯片。由于它体积小&#xff0c;兼容性&#xff0c;性价比高而深受单片机爱好者及企业欢迎&#x…

Java_笔记_static_静态变量方法工具类_main方法

static表示静态&#xff0c;是Java中的一个修饰符&#xff0c;可以修饰成员方法和成员变量。 一、静态变量: 被static修饰的成员变量 1.特点&#xff1a; 1&#xff09;被该类所有的对象共享。 2&#xff09;不属于对象&#xff0c;属于类。 3&#xff09;随着类的加载而加载…

(个人记录)Ensight后处理EDEM学习笔记

①EDEM计算完毕 ②左上角,点击输出data 先点击一下 然后再把EXPORT FORMAT改为ensight的格式 file name选择要保存到的文件夹,保存格式为.case文件 设置保存的时间步 填入“10”的意思是0.01*10=0.1,在ensight中以0.1的时间步展示结果 点击queries 在ensight里只能…

七段显示译码器

我们会把它放在系统中来显示多位&#xff0c;很多位的表达会用到小数点 12.34 我只想显示&#xff0c;我现实系统的数字大小有一个范围&#xff0c;比如整数四位&#xff0c;小数四位 我显示12.34 就意味着首先两位就是空着&#xff0c;最后两位也是空着 这几位从输入的数字…

Android 基础知识4-1 用户界面简介VIewGroup、Onclick事件处理

引言&#xff1a; 一个好的应用界面的必备条件是&#xff1a;内容清楚、指示明白、屏幕美观和有亲切感。界面通常包含图形和文字。应用界面的设计是对控件进行适当的取舍及功能的选择和处理的过程。在程序设计中&#xff0c;需要对设计的方法反复推敲、琢磨&#xff0c;才能使其…