背景:最近在做多线程方面的工作,工作中发现多线程中很多坑,这里就有一个List添加对象的误区,这里做个分享跟大家讲讲这个坑是怎么形成的怎么样避免。
示例:
代码及错误:
如果单单只从程序逻辑上看,应该没有逻辑错误,但是结果却是是有为空值的情况,这时候有些多线程经验的读者可能会想到,构造函数也是一个函数,有可能在往List中添加对象的时候,构造函数还没有将对象返回就执行了添加操作,造成了这个问题的出现,下面我们来验证一下这个观点是否正确。
从图中可以看到,在对象new之后立即执行了取属性操作,如果构造函数没有返回立即执行后面肯定会出现空指针异常,但是这里并没有出问题,说明不是构造函数返回结果的问题,同时也说明了构造函数是具有线程安全的。
为此可以怀疑这个问题是优于List.Add方法造成的,为此,查看一下List.Add方法的源码可以了解其中的原委。
点开this.EnsureCapacity(this.size+1);方法,如下图所示:
这时候,我们就可以猜测到问题就出现在这个容量扩展方法这里了,于是我尝试着修改List的最初容量,使之不需要进行容量扩展,此时程序运行正常,说明问题的确就在这里。
虽然说问题解决了,问题的原因也知道了,但是为什么会有这样的问题呢?内存扩容是如何形成这个错误的呢,于是和六爷讨论了一下这个问题,很感谢六爷指点迷津,让我知道了这其中的具体原因,我画个简图给大家讲解一下,希望大家能看明白:
验证:
假设真的是这个原因造成的应该出现空值的位置应该都是2的整数次幂之后的值,并且如果内存越大拷贝的时间越长,出现空值的几率就越大,于是改造程序,可以验证六爷的这个猜测:
代码:
结果,8192=4096*2,16384=8192*2:
解决方案:
1.扩容List的初始容量为集合需要的实际容量或更大
2.给List.Add方法加锁
3.使用List的线程安全版本,如下图所示:
源代码: