面试官问 : ArrayList 不是线程安全的,为什么 ?(看完这篇,以后反问面试官)
前言
金三银四 ?
也许,但是。
近日,又收到金三银四一线作战小队成员反馈的战况 :
我不管你从哪里看的面经,但是我不允许你看到我这篇文章之后,还不清楚这个面试问题。
本篇内容预告:
ArrayList 是线程不安全的, 为什么 ?
① 结合代码去探一探所谓的不安全
② 我们弄清楚为什么不安全(结合源码以及我的个人讲述)
③ 不止步于为什么, 我们得知道怎么办(方案以及结合源码分析)
ps: 这篇文章 注定篇幅很长, 我会从非常非常小白0基础的角度去 很啰嗦地去讲一些内容。
距离上一次 这么臭长去讲 list集合相关的问题,还是21年的时候 ,个人认为也是很有学习价值的,大家也可以看看,但是注意就是 ,别看着看着回不来了,也是上万文字+图片+源码分析的文章:
Java 移除List中的元素,这玩意讲究!
开整开整。
正文
看看它的不安全 以及 为什么不安全
线程不安全 ,看看官腔怎么说:
线程不安全,是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
其实小白话就是 :
多线程操作的时候 ,容易出现与我们预想不一致的结果。
就比如说,你做好准备 接我两拳。
本来你以为 我是打完一拳再打一拳。
结果我直接一招双龙出海,两只手一起打你, 你顶得住么?(你根本防不住。)
开始结合代码一探究竟。
代码小栗子 ① :
public static void main(String[] args) {int threadNum = 1;List<String> resultList = new ArrayList<>();for (int i = 0; i < threadNum; i++) {new Thread(() -> resultList.add(UUID.randomUUID().toString())).start();}System.out.println("我们最终得到的resultList大小:"+resultList.size());}
代码简析:
大家猜想结果是多少 ?
是 0 , 为什么不是 1 ? 为什么会出现 0 ? 不是往里面ADD 了一个元素么 ?
如果说你对这个 0 的结果很意外的话, 兄弟,你完了。
(吓你的,你本来要完了,还好你今天遇到了我)。
对这个 0 的结果很意外,代表你对线程方面的基础知识,可能还没了解。
简析:
因为for 里面 开了一个新的线程 new Thread , 这个线程 负责往 list 里面 add 一个数据。
但是 我们的打印 list.size 是 主线程 , 也就是说,如果 在 新的线程 new Thread 没执行完add 方法, 主线程就执行打印的代码,
那么就是 0啊 。
所以就是说,我们 主线程 等一等,让 for循环里面的新的线程 new Thread 先插入数据。
public static void main(String[] args) throws InterruptedException {int threadNum = 1;List<String> resultList = new ArrayList<>();for (int i = 0; i < threadNum; i++) {new Thread(() -> resultList.add(UUID.randomUUID().toString())).start();}sleep(1000);System.out.println("我们最终得到的resultList大小:"+resultList.size());}
可以看到结果是1了 :
接下来我们把线程数改成10(另外主线程等5秒,给足够的时间让这个10个线程好好竞争一下) ,我们来看看 所谓的不安全 的ArrayList 能出现什么 ‘不安全’ :
public static void main(String[] args) throws InterruptedException {int threadNum = 10;List<String> resultList = new ArrayList<>();for (int i = 0; i < threadNum; i++) {new Thread(()->{resultList.add(UUID.randomUUID().toString().substring(0,8));System.out.println(resultList);}).start();}sleep(5000);System.out.println("我们最终得到的resultList大小:"+resultList.size());}
情况①:
正常运行的情况,可以看到 10个线程 不争不抢 :
显然这是不符合我们文章主题的,我们要看的是不安全。
情况②:
有竞争,但是线程们 很友好,所以也没出什么幺蛾子(仅仅对于往list塞数据这个动作来说)
情况③:
10个线程 显然还是 太少了, 而且我电脑机子又好, 终于出现 ‘不安全’情况了 ,非常难得。
多线程操作 ArrayList 导致出现 add赋值 出现 null 情景分析 :
为什么会出现,先看看源码 ,
Object[] elementData : 保存所有元素值的 数组
size : elementData中存储的元素个数
再看看 add 函数的 源码 :
ensureExplicitCapacity ()函数:
将当前的新元素加到列表后面,判断列表的 elementData 数组的大小是否满足。如果 size + 1 的这个需求长度大于 elementData 这个数组的长度,那么就要对这个数组进行扩容。
elementData[size++] = e :
e是传入的 值, 把这个值 赋值在 elementData数组的 size++ 位置 。
大家看出来问题没?
这两步没有和在一块操作。
也就说如果出现这个扩容的触发 和后面 赋值 并发情况 ,那么就有好戏看了。
ArrayList是基于数组实现,数组大小一旦确定就无法更改。
ArrayList的扩容: 将旧数组容器的元素拷贝到新大小的数组中(Arrays.copyOf函数)。
而 通过new ArrayList<>()实例的对象初始化的大小是0,所以第一次插入就肯定会触发扩容。
这里又必须给大家推荐一篇好文章了:
(没错也是我写的,但是看到这,你别去看这篇,跟着我现在的思路继续分析 这个null值出现的情景,实在很感兴趣,自己一会再看)
Java ArrayList new出来,默认的容量到底是0还是10 ?
看看我们的截图, 第一个数据是 null 。
有趣。
第一个数据是 null (其实应该称为 执行扩容操作,并发导致出现null值 )分析 :
第一个线程A 插入数据时 属于首次add ,发现需要扩容, ok , 线程A 去扩容去了。
然后 我们是多线程操作场景, for循环第二次,触发new第二个线程B来了,线程B去add的时候,
因为线程A第一次扩容可能并没完成,所以导致 线程B 扩容所拿到list的elementDate是旧的,并不是线程A第一次扩容后对象, 线程B 拿到的 size还是 0 ,所以线程B 也认为自己是第一次add ,也需要扩容。
幻想一下 A 、B 线程的并发 一起进入扩容场景:
那么线程A 是第一次add的时候,他知道他要去扩容, 他自己 扩容 完,自己整了个list的新elementDate ,然后 就开始赋值 elementDate[size++] = A的UUID值。
在线程A这个操作的过程中,线程 B 在做什么?
线程 B一开始 不巧也是以为要扩容,他拿着一个旧的 list的elementDate 也整了一个新的数组 ,
然后把 整个 list的 elementDate 引用指向 B线程自己弄出来的对象
this.elementData = B新构建的对象(这对象全部值为null);
然后做什么?
然后 线程B 开始执行 elementDate[size++] = B的UUID值。
这里的好玩点是什么?
线程A 的值 赋值在 他创建出来的 elementDate 里面,然后触发 size++ 。
但是线程 B 呢, 把 this.elementData 指向了自己的新弄出来的, 所以 A 的值 无情被抛弃, 但是 线程 B 开始赋值的时候,
看看这个size在源码里的情况:
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{transient Object[] elementData;//这是大家共用的 size private int size;
}
size是大家共用的, size 被 线程A 加1了 ,所以就出现 线程 B 赋值的时候 执行 elementDate[size++] = B的UUID值,出来的结果是
[null , B的UUID值]
null 就是这么来的 ! 能看到这的人,友情提示,你已经阅读了3500字。当然还没完事。
情况④:
java.util.ConcurrentModificationException 并发冲突
直接定位报错函数:
这个其实 之前分析过:
modCount是修改记录数,expectedModCount是期望修改记录数;
初始化的时候 expectedModCount=modCount ;
ArrayList的add函数、remove函数 操作都有对modCount++操作,当expectedModCount和modCount值不相等, 那就会报并发错误了(其实这个不是仅仅是多线程的问题,是这个ArrayList 代码next函数的问题,更多细节可以有空看看 Java 移除List中的元素,这玩意讲究!)。
那么到这 我们大概知道 这个 ArrayList的不安全 问题了, 说白了就是 2行代码没上锁操作。
怎么办? 怎么安全起来?
最简单的方式, 也是面经上经常看到的 使用 Vector :
List<String> resultList = new Vector<>();
看看vector怎么保证安全的:
其次 是 使用 Collections里面的synchronizedList :
List<String> resultList =Collections.synchronizedList(new ArrayList<>());
看看synchronizedList 怎么保证安全的:
还有可以使用 CopyOnWriteArrayList :
List<String> resultList = new CopyOnWriteArrayList();
看看CopyOnWriteArrayList 怎么保证安全的:
ps:
CopyOnWriteArrayList 的set 也是上锁
但是get 没有, 也就是说,get可能在多线程场景使用,拿到的是旧数据是可能的(也就是当前能读到的list里面的数据)
那么就CopyOnWriteArrayList的 set\add\get 函数,你能预料到它的不好点么?
1.set add 都选择使用了Arrays.copyOf复制操作
所以存在 内存占用以及耗时问题,当数组元素越来越多的时候。
2. get 多线程过程读取数据不是实时,那就可能出现 数据不一致问题,但是最终数据是一致的(读多写少就很合适)。
好了,该篇就到这吧。
相关文章:
面试官问 : ArrayList 不是线程安全的,为什么 ?(看完这篇,以后反问面试官)
前言 金三银四 ? 也许,但是。 近日,又收到金三银四一线作战小队成员反馈的战况 : 我不管你从哪里看的面经,但是我不允许你看到我这篇文章之后,还不清楚这个面试问题。 本篇内容预告: Array…...
Linux串口应用编程
一、 串口API 在Linux系统中,操作设备的统一接口就是:open/ioctl/read/write。 对于UART,又在ioctl之上封装了很多函数,主要是用来设置行规程。 所以对于UART,编程的套路就是: open设置行规程,比如波特率、数据位、停止位、检验位、RAW模式、一有数据就返回read/write 怎么设置…...
java程序员学前端-HTML篇
HTML 与 CSS HTML 是什么:即 HyperText Markup language 超文本标记语言,咱们熟知的网页就是用它编写的,HTML 的作用是定义网页的内容和结构。 HyperText 是指用超链接的方式组织网页,把网页联系起来Markup 是指用 <标签>…...
【云原生|Docker】03-docker的基础操作
目录 前言 查询相关 容器相关 1. 容器启动 2. 容器关闭 3. 重启容器 4. 暂停容器 5. 删除容器 6. docker run参数汇总 镜像相关 1. 镜像推送至仓库 2. docker image load使用 3. docker image import使用 4. dokcer image参数汇总 前言 容器的命…...
vue2+高德地图web端开发使用
创建vue2项目我们创建一个vue2项目,创建vue2项目就不用再多说了吧,使用“vue create 项目名 ”创建即可注册高德地图高德地图官网地址:https://lbs.amap.com/如果是第一次使用,点击注册然后进入我们的控制台注册完之后进入控制台&…...
01背包问题c++
问题 问题介绍 有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。 第 i 种物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。 输入格式 第…...
ZYNQ硬件调试-------day2
ZYNQ硬件调试-------day2 1.ILA(Integrated Logic Analyzer ) 监控逻辑内部信号和端口信号;可以理解为输出。可单独使用 2.VIO(Virtual Input/Output ) 实时监控和驱动逻辑内部信号和端口信号,可以理解为触发输入。不可…...
JavaScript中Promise的简单使用及其原理
Promise是ES6最重要的特性之一,今天来系统且细致的研究一下Promise的用法以及原理。 按照我往常的理解,Promise是一个构造函数,有all、resolve、reject、then、catch等几个方法,一般情况下,在涉及到异步操作时才会用到…...
SpringBoot RabbitMQ 延时队列取消订单【SpringBoot系列14】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 1 项目准备 SpringBoot 雪花算法生成商品订单…...
【论文阅读 WWW‘23】Zero-shot Clarifying Question Generation for Conversational Search
文章目录前言MotivationContributionsMethodFacet-constrained Question GenerationMultiform Question Prompting and RankingExperimentsDatasetResultAuto-metric evaluationHuman evaluationKnowledge前言 最近对一些之前的文章进行了重读,因此整理了之前的笔记…...
ouc 网络安全实验 格式化字符串漏洞
文章目录要求lab1lab2lab3lab4结语因为当时自己做实验的时候出现了很多疑问不会解决,在网上看到了一位大佬 王森ouc 的专栏文章解决了很多问题,也学到了很多知识和解决问题的方法,现在把我的实验解决方法也发上来,希望有不会的同…...
PMSM矢量控制笔记(1.1)——电机的机械结构与运行原理
前言:重新整理以前的知识和文章发现,仍然有许多地方没有学得明白,懵懵懂懂含含糊糊的地方多如牛毛,尤其是到了真正实际写东西或者做项目时,如果不是系统的学习了知识,很容易遇到问题就卡壳,也想…...
2022年全国职业院校技能大赛(中职组)网络安全竞赛试题——中间人攻击渗透测试解析(详细)
B-4任务四:中间人攻击渗透测试 *任务说明:仅能获取Server4的IP地址 *任务说明:仅能获取Server11的IP地址 1.通过上题渗透后得到控制权限的服务器场景Server4进行查看本地的arp缓存表的操作,并将该操作所使用的命令作为Flag值提交; 2.通过上题渗透后得到控制权限的服务…...
MySQL必知必会 | 安全、维护、性能
全球化和本地化 关于MySQL处理不同字符集和语言 字符集和校对顺序 数据库被用来存储和检索数据,不同的语言和字符集需要以不同的方式存储和检索,因此,MySQL需要适应不同的字符集,适应不同的排序方式 一些术语: 字符…...
MaaS Model as a Service 模型即服务
大模型是人工智能的发展趋势和未来。大模型是“大算力强算法” 结合的产物。目前,大模型生态已初具规模。大模型能够实现 AI 从“手工作坊”到“工厂模式”的转变,大模型通常是在大规模无标注 数据上进行训练,学习出一种特征和规则…...
【编程基础】027.C语言中函数在解题中的应用(三)
文章目录C语言中函数的应用1、自定义函数实现二维数组的转置2、自定义函数之整数处理3、自定义函数之数字后移4、自定义函数之字符串拷贝C语言中函数的应用 1、自定义函数实现二维数组的转置 题目描述 写一个函数,使给定的一个二维数组(3&a…...
echart图表之highcharts
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、HighCharts是什么?二、使用步骤1.引入库2.前端代码3.展现结果4.后台自动截图总结前言 提示:这里可以添加本文要记录的大概内容&…...
关于.Net和Java的看法——我见过最牛的一个小实习生经历
1、背景 笔者(小方同学在学习)是一个专科院校的一名普通学生,目前就职于某三线城市的WEB方面.Net开发实习生,在找实习期间和就业期间的一些看法,发表此文,纯个人想法,欢迎讨论,指正…...
基于springboot+vue的“智慧食堂”程序设计实现【毕业论文,源码】
系统登录界面系统架构开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7数据库工具:Navicat开发软件:eclipse/myeclipse/ideaMaven包:Maven浏览器…...
学计算机选择什么编程语言好一些?
工资水平的话,目前人工智能、大数据和云计算等领域的工资相对较高,但是要求也高,学历,学习能力什么的。然后是后端开发,Python、Java、C等编程语言的工资普遍较高。 不用开发语言的优势 Java:Java是一种…...
持续集成 在 Linux 上搭建 Jenkins,自动构建接口测试
本篇把从 0 开始搭建 Jenkins 的过程分享给大家,希望对小伙伴们有所帮助。 文章目录 在 Linux 上安装 Jenkins在 Linux 上安装 Git在 Linux 上安装 Python在 Linux 上安装 Allure配置 Jenkinsjenkins 赋能 - 使用邮箱发送测试报告jenkins 赋能 - 优化测试报告内容…...
MySQL学习笔记(总结)
1. 数据库服务器操作命令 启动数据库:net start mysql80 (注释:windows命令) 停止数据库:net stop mysql80 (注释:windows命令) 重启数据库:systemctl restart mysql;…...
Android开发 Layout布局 ScrollView
1.LinearLayout 属性 orientation:内部组件排列方式,可选vertical、horizontal,默认horizontal layout_weight: 与平级组件长宽比例,需要将layout_width、layout_height其中一个设置为0dp,表明长或宽与平级组件的长…...
手撕数据结构与算法——树(三指针描述一棵树)
🏆作者主页:king&南星 🎄专栏链接:数据结构 🏅文章目录🌱树一、🌲概念与定义二、🌳定义与预备三、🌴创建结点函数四、🍀查找五、🍁插入六、&a…...
字节跳动Java后端开发实习面经
最近在和同学一起找实习,投了b站、字节和miHoYo的后端开发。b站二月底就投了,但现在也还没回复;miHoYo也还没回复,估计是只面向24届了;感谢字节,给了我面试的机会。字节真的处理好快,不到一周官…...
STM32实战项目-触摸按键
前言: 通过触摸按键控制LED灯以及继电器,具体实现功能如下: 1、触摸按键1单击与长按,控制LED1; 2、触摸按键2单击与长按,控制LED2; 3、触摸按键3单击与长按,控制LED3; 4、触摸按键4单击与长…...
安全行业-术语(万字)
肉鸡 所谓“肉鸡”说一种很形象的比喻,比喻那些可以任意被我们控制的电脑,对方可以是Windows系统,也可以说UNIX/linux系统,可以说普通的个人电脑,也可以是大型的服务器,我们可以像操作自己的电脑那样来操控…...
P1113 杂务(拓扑排序 or 记忆回溯)
题目描述 John的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它。比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及一些其它工作。尽早将所有杂务完成是必要的,因为这样才有更…...
Web3中文|政策影响下的新加坡Web3步伐喜忧参半
如果说“亚洲四小龙”是新加坡曾经的荣耀,那么当时代进入21世纪的第二个十年,用新加坡经济协会(SEE)副主席、新加坡新跃社科大学教授李国权的话来说,新加坡现在的“荣耀”是全球金融的主要“节点”或区块链行业发展的关…...
Java数据库高阶面试题,好程序员学员分享百度Java面试流程
小源下面分享一位好程序员的学员去百度Java面试流程!百度技术一面(20分钟)1、自我介绍很流畅捡重点介绍2、数据结构算法好不好挺好的(其实心还是有点虚,不过最近刷了很多好程序员出的题感觉没问题!)3、找到单链表的三等分点,如果单…...
wordpress怎么static/seo服务公司上海
最近学习EntityFramework,于是接触了LinqPad这款享誉已久的软件,深感相见恨晚。软件具体不多做介绍了,只简单介绍下使用方法。数据库操作添加数据库连接1,首先通过点击Add connection打开Choose Data Context窗口2,选择Defalt(LINQ to SQL)3,…...
深圳企业公司做网站/营销咨询公司经营范围
本质复杂性 偶然复杂性Kubernetes可能是当前开源人群的宠儿,但是Hadoop早已受到人们的尊敬。 Hadoop最终用光了天然气,因为它难以使用。 正如Capital One的Bernard Golden所说 , Kubernetes虽然取得了长足进步,但仍然“没有野餐的…...
dw简易网站怎么做/徐州seo招聘
小编典典似乎您可以从Java图形上下文的中获得更准确的几何图形FontMetrics。附录:解决此问题时,可能有助于区分模型和视图。模型是StringUTF-16代码点的有限序列,而视图是一系列字形,以某种字体在某些设备上呈现。在Java的特定情况…...
wordpress自制主题/今晚赛事比分预测
大数据人才作为中国互联网行业需求最旺盛的六类人才之一,初级大数据工程师的薪水就达15k,有三年以上工作经验的资深工程师更是高达年薪50-60万。据麦肯锡报告,目前大数据人才缺口更是在百万人以上。如何实现大数据开发入门呢?如果…...
网站只能手机打开代码/免费推广seo
以一台 Linux 服务器为例。这台 Linux 包括两颗 Intel(R) Xeon(R) CPU E5-2630 v4 2.20GHz CPU, 单颗 CPU 包括 10 个 cpu core, 使用超线程包含 20 个逻辑 cpu core, 具体的官方介绍: E5-2630 V4。下面让我们通过 Linux 的命令来查找对应的参数,看看是…...
PHP是做网站最好的/搜索关键词的工具
a [i for i in range(10) if not(i % 2) and i % 3] #一假则假 print(a)相当于: #youtube 小甲鱼搬运:因为true1 false0 如果能被2整出那么余数为0所以要用nota range(10) b [] for i in a:if not (i % 2) and i % 3 :b…...