写入null_ArrayList并发写出现Null值

ArrayList并非线程安全的容器,这一点大家可能都非常清楚,但是在并发写入的情况下,不安全的情况具体有哪些,大家是否很清楚呢?本篇文章重点聊一下出现null的情况,然后对于其他并发写的安全做一个简单的叙述

我们看下面的代码,打印List的元素数量以及打印存储的元素

        List list = new ArrayList<>();        for (int i=0;i<10;i++) {            int finalI = i;            new Thread(()->{                list.add(finalI +1);            }).start();        }        System.out.println(list.size());        System.out.println(list.toString());

最理想的情况下,打印结果应该如下:

10[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但是有可能出现一些其他问题,就像下面结果List元素出现null值的结果

10[null, 1, 3, 4, 5, 6, 7, 8, 9, 10]或者10[null, 2, 3, 4, 5, 6, 7, 8, 9, 10]或者10[null, null, null, 1, 5, 6, 7, 8, 9, 10]......

在我看百度看到的所有答案中,关于并发写出现Null值,几乎都是将原因归咎到add方法中的size++上,这里我个人认为这种回答应该是错误的,出现null值的原因应该是扩容所造成的。

 public boolean add(E e) {        ensureCapacityInternal(size + 1);        elementData[size++] = e;}

首先说一下为什么我觉得网上的答案是错误的,我们模拟add方法,然后使用javap命令拿到class的字节码看一下:

#### Java程序int size = 0;int[] elementDate = new int[5];public void add() {        elementDate[size++] = 10;}#### Javap 得到的字节码    public void add();  Code:       0: aload_0       1: getfield      #3 // Field elementDate:[I       4: aload_0       5: dup       6: getfield      #2// Field size:I       9: dup_x1      10: iconst_1      11: iadd      12: putfield      #2// Field size:I      15: bipush        10      17: iastore      18: return

在add方法的字节码中,通过getfield拿到elementDate数组放入栈顶(操作数栈),然后dup命令复制栈顶的数组并将复制值压入栈顶,然后再通过getfield获取size数值,下一步dup_x1命令会将栈顶的数值size复制两份,并将两个复制值压入栈顶,然后iconst_1命令将数值1压入栈顶,再使用iadd命令对栈顶的两个元素进行相加,并通过putfield将size更新,最后iastore更新数组(因为dup_x1复制了两份,所以数组的索引仍然是更新前的size)。大家可以好好想一下这个操作,无论size++多么不安全,因为索引复制两份被保存的操作数栈中,所以不可能在list中出现null值,只会出现覆盖的可能。

如果大家理解了上面的过程,我们思考下为什么null值出现了呢?由于ArrayList是基于数组实现,由于数组大小一旦确定就无法更改,所以其每次扩容都是将旧数组容器的元素拷贝到新大小的数组中(Arrays.copyOf函数),由于我们通过new ArrayList<>()实例的对象初始化的大小是0,所以第一次插入就会扩容,由于ArrayList并非线程安全,第二次插入时,第一次扩容可能并没完成,于是也会进行一次扩容(第二次扩容),这次扩容所拿到list的elementDate是旧的,并不是第一次扩容后对象,于是会因为第一次插入的值并不在旧的elementDate中,而将null值更新到新的数组中。这里我们举一个详细的例子:

现在有线程A和B分别要插入元素1和2,当线程A调用add方法时size是0,于是会进行一次扩容,此时线程B调用add方法时size仍然是0,所以也会进行扩容,假设此时线程A比线程B扩容先完成,此时list的elementDate是新的数组对象(由线程A构建),然后开始执行elementDate[size++] = 1的程序,这个过程中线程B扩容拿到的数组仍然是旧的elementDate,于是线程B构造一个新的数组(数据全部为null),然后使list的elementDate指向线程B构造的对象,那么线程A之前构造的elementDate也就被丢掉了,但是由于size已经自增,所以线程B会在索引为1的位置赋予2,那么此时数组元素就成了[null,2],当然如果线程B扩容比线程A先完成那么就可能为[null,1]。

大家如果在初始化的时候就已经开辟好足够大的容量,那么就不会出现上面的问题,关于上面的解释大家可以作为参考,因为不同的编译器可能javap得到的字节码可能会不同吧(这里我编译结果是size被复制两份,然后使用其中的一份加一更新到size中,然后用复制的另一份作为索引更新数组,但是网上得到信息大家都认为是数组先赋值,然后size自增)。

除了上面元素为null的情况外,还会有其他错误

  • 数量错误,集合数据正确
9[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
f28e4123825e22a2e68a9e8626545edc.png
9[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

大家是不是第一反应是不是觉得这种结果是由ArrayList本身的不安全特效造成的呢?实际上这种结果和ArrayList本身没有关系,只是因为我们打印不具有原子性所造成的。因为我们启用了多线程,主线程调用size方法时,可能多线程内部对list还在继续执行增加元素的操作,当主线程调用toString方法时,多线程已经执行完毕,所以元素数量正确,当然也有可能你调用toString方法时,多线程仍然未执行完,此时size和toString结果都不正确,如下:

8[1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 覆盖,这种情况的原因在上面的分析中以及提到,因为size++并不是原子性的,所以可能线程A自增的时候,线程B也进行一次自增,但是两次自增的结果是一样的,所以先完成的线程更新的数据会被后完成的线程覆盖掉

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

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

相关文章

c语言函数调用数组_第七讲:C语言基础之函数,第二节,实现汉诺塔

本文约2200字&#xff0c;主要讲了C语言基础之函数&#xff0c;递归&#xff0c;数组作为函数参数以及案例、练习题等。带你进入C语言的世界&#xff0c;入门C语言&#xff0c;后边将持续更新。可以收藏学习。想了解C语言基础之函数&#xff0c;函数的基本概述&#xff0c;函数…

当代国人绝难做到的10件平常事

1、在饭厅中保持低声。无论是在家中&#xff0c;还是在大饭店吃饭&#xff0c;中国人总爱高声喧哗&#xff0c;如果是饮酒&#xff0c;还会扯酒皮&#xff0c;猜拳行令&#xff0c;完全不把旁人放在眼中。因而&#xff0c;在中国&#xff0c;也没有人觉得饭厅噪音得治一治。事实…

仍然报错_only_full_group_by配置,竟让所有应用报错?

推荐学习周一福利到&#xff01;献上“独家全新”MySQL进阶套餐&#xff0c;简直就是血赚 全网独家的“MySQL高级知识”集合&#xff0c;骨灰级收藏&#xff0c;手慢则无 1. 踩坑经历一个很平常的下午&#xff0c;大家都在埋头认真写bug呢&#xff0c;突然企业微信群里炸锅了&a…

根据录入的计算公式计算_污水处理工程设计入门(10)—堰口计算

呓语&#xff1a;虽然现在很多人会推送很多的快速计算表格&#xff0c;但是我并不想授人以鱼。我希望每一个想学习想入门做污水处理工程设计的人先看懂如何设计&#xff0c;再去想如何快速计算&#xff0c;再去想怎么改进。再者&#xff0c;想想有一天你当领导审图了&#xff0…

DNN SEO专题 (收集)

SEO, 是Search Engine Optimization的缩写, 翻译过来是“搜索引擎优化", 直白说就是“针对搜索引擎所做的优化处理工作”。 是一种利用搜索引擎的搜索规则来提高目的网站在有关搜索引擎( Google, Baidu, Live Search 等) 内提名的方式。它可以给你的网站带来更多的客户访问…

发送请求_发送soap请求调用WSDL

");soap.Append("");soap.Append("");soap.Append("XX系统");soap.Append("");soap.Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));soap.Append("");soap.Append("{"LoginName":&qu…

递归函数python有什么特点_Python中的递归

在前面的讲解中&#xff0c;函数的调用通常发生在彼此不同的函数之间。其实&#xff0c;函数还有一种特殊的调用方式&#xff0c;那就是自己调用自己&#xff0c;这种方式称为函数递归调用。 递归&#xff0c;在程序设计中也是一个常用的技巧&#xff0c;甚至是一种思维方式&am…

XML基本知识(三)

XML语法(3)--属性、声明符号、字符数据、注释 属性&#xff1a;提供元素的附加信息。 属性是元素开始标签的一部分。eg: <para keywords"Napoleon,France,history">...</para>注意: 属性由“名”和“值”组成&#xff0c;中间有“”分隔。值由引号包括…

apache实验报告 linux_Linux实验报告

Linux实验报告-源代码编译安装Apache(Tarball文件安装)实验背景&#xff1a;通常GNU组织提供的程序包都是源代码格式&#xff0c;即将软件的所有源码文件先以tar打包&#xff0c;然后再使用gizp或是bzip2压缩&#xff0c;生成一个.tar.gz或是.tar.bz2结尾的软件包&#xff0c;也…

python合并两个文本文件内容_用Python 将两个文件的内容合并成一个新的文件.

一个文件的内容是: Introduction to Programming, Networking Fundamentals, Internetworking Technologies, Platform Technologies, Information Technology for Users, Computer Forensics, Enterprise Networks, Database Technologies 还有一个是: BN108, MN401, MN503, B…

设备场景函数——72个

说明&#xff1a;本类 API 函数&#xff0c;内容均摘自网络&#xff0c;版权归实际作者所有。 CombineRgn 将两个区域组合为一个新区域 CombineTransform 驱动世界转换。它相当于依顺序进行两次转换 CreateCompatibleDC 创建一个与特定设备场景一致的内存设备场景 CreateDC,Cre…

安卓手机突然很卡_你的安卓手机越来越卡?教你4招轻松解决问题!

原标题&#xff1a;你的安卓手机越来越卡&#xff1f;教你4招轻松解决问题&#xff01;现在我们对于手机的使用时间不会特别的长&#xff0c;所以这个更换的速度就比较快。其实是很多的用户换手机是因为比较喜欢用新的手机体验新的科技&#xff0c;不过也还是有很多的用户不是因…

python 导入自己写的类_python中自己的类不能被导入

我自己编写了一个类 class Settings(): def __init__(self): self.screen_width1200 self.screen_height800 self.bg_color(230,230,230) 然后我想在引用 import sys import pygame from settings import Settings def run_game(): pygame.init() ai_settingsSettings() screen…

关于使用在webforms里使用routing遇到的问题

看了重典的两篇文章 System.Web.Routing入门及进阶 上篇 System.Web.Routing入门及进阶 下篇 仿照其中的进行了操作&#xff0c;发现怎么不都起作用&#xff0c;非常奇怪&#xff0c;搜了好多才找到了解决方案 是在MSDN上找到的&#xff0c;如下&#xff1a; To configure …

python调用父类构造函数需要放在第一句吗_Python继承和调用父类构造函数

这是我正在Python中做的&#xff1a;class BaseClass:def __init__(self):print The base class constructor ran!self.__test 42class ChildClass(BaseClass):def __init__(self):print The child class constructor ran!BaseClass.__init__(self)def doSomething(self):prin…

python千位分隔符_python – 为pandas数据帧中的整数设置千位分隔符

我正在尝试使用{&#xff1a;,}’.格式(数字),如下例所示,格式化pandas数据帧中的数字&#xff1a; # This works for floats and integers print {:,}.format(20000) # 20,000 print {:,}.format(20000.0) # 20,000.0 问题是,对于具有整数的数据帧不起作用,并且在具有float的数…

[导入]【翻译】WF从入门到精通(第十章):事件活动

摘要: 学习完本章&#xff0c;你将掌握&#xff1a;1.使用HandleExtenalEvent活动创建特定的事件处理程序2.在你的工作流中使用Delay活动3.在你的工作流中使用EventDriven活动4.在你的工作流中使用Listen活动5.理解EventHandlingScope活动在活动并发执行的情况下是怎样监听事件…

mysql查询返回xml格式_MySQL数据库查询操作XML的经验分享

mysql里面有内置的操作xml的函数。分别是ExtractValue()和UpdateXML()函数。语法&#xff1a;1EXTRACTVALUE (fiedname, XPathstring);第一个参数&#xff1a;fiedname是String格式&#xff0c;为表中的字段名第二个参数&#xff1a;XPathstring (Xpath格式的字符串) &#xff…

DOM解析原理

DOM解析原理 关键字: xml w3c dom属性和方法用于处理XML文档的DOM元素属性 属性名 描述 childNodes 返回当前元素所有子元素的数组 firstChild 返回当前元素的第一个下级子元素 lastChild 返回当前元素的最后一个子元素 nextSibling 返回紧跟在当前元素后面的元素 no…

wordpress发布模块_如何用WordPress打造出一个类似知乎的问答站点

像打造一个像知乎这样的在线问答社区吗&#xff1f;问答网站非常有意思&#xff0c;而且用户活跃度也高&#xff0c;有很多非常有用的信息。在这篇文章中&#xff0c;我们将向你展示如何在没有任何编程经验的情况下利用WordPress快速打造一个问答类网站。你可以将整个网站做成问…