网站模版是什么意思,网页怎么弄到桌面快捷方式,镇江高端网站建设,扁平式网站seo 内链ArrayList并非线程安全的容器#xff0c;这一点大家可能都非常清楚#xff0c;但是在并发写入的情况下#xff0c;不安全的情况具体有哪些#xff0c;大家是否很清楚呢#xff1f;本篇文章重点聊一下出现null的情况#xff0c;然后对于其他并发写的安全做一个简单的叙述我…ArrayList并非线程安全的容器这一点大家可能都非常清楚但是在并发写入的情况下不安全的情况具体有哪些大家是否很清楚呢本篇文章重点聊一下出现null的情况然后对于其他并发写的安全做一个简单的叙述我们看下面的代码打印List的元素数量以及打印存储的元素 List list new ArrayList(); for (int i0;i10;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]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也进行一次自增但是两次自增的结果是一样的所以先完成的线程更新的数据会被后完成的线程覆盖掉