一.线程安全问题重现
根据报错我们可以追溯到在我们代码的list.toString()方法的调用出抛出了异常ConcurrentModificationException
具体排除异常的ArrayList父类中的行号:AbstractCollection.toString(AbstractCollection.java:461)
ArrayList$Itr.next(ArrayList.java:861) 由此我们发现.toString()调用了迭代器的it.next();
ArrayList$Itr.checkForComodification(ArrayList.java:911) 最后迭代器next()又调用了:checkForComodification(); 因此最终真正抛出异常的代码被我们找到了
我们发现此处的checkForComodification方法对于当前集合中数据量做了一个判断,检查集合中新增元素个数 和代码执行时现有元素个数比较,不一致抛出异常
最终通过上述的问题重现与分析我们可以得到一个结论那就是ArrayList显然是一个线程不安全的集合
那么,当在高并发的场景下有哪些线程安全的List集合可以使用呢。下述是常见的集合线程安全的List集合。
同步容器: 使用锁保证线程安全,多线程并发调用方法时基本上都是串行执行 Vector、SynchronizedList
并发容器:使用其他技术手段,支持高并发读写 而且数据基本安全 Cow容器
二.安全性验证
Vector:
查看Vector的方法实现,发现其为实现线程安全将所有成员方法都通过synchronized加锁了。并发写可以保证安全,并发读也加锁了,性能差!
SynchronizedList:
以装饰者模式,通过实现与传入者相同接口的父类接口List,并持有一个组件对象的引用(即传入的ArrayList对象)。装饰者可以通过其子类扩展组件的功能(对原有方法添加synchronized锁),对除了迭代器以外的方法使用同步代码块加锁。由此将线程不安全的List转为线程安全的
COW容器:写时复制技术实现的并发容器类
具体实现原理:
分析上述代码我们可以发现其添加了lock锁,并且在获取原数组的引用对象和长度后又拷贝生成一个长度+1的新数组。此时新数组的前len位都是空的,然后在最后一位添加增添的元素并将新数组引用设置给原数组变量接收。由此便完成了集合元素的增加。
三:性能分析
Vector:
COW容器:
SynchronizedList:
总结:
Vector: 虽然读写方法都加锁了,但是性能比Cow容器要好
CopyOnWriteArrayList: 数据复制后,原数组没有变量持有引用,GC时会被回收,频繁导致GC。GC时间过长:并发写性能非常差,读未加锁,高并发读少量写适用
SynchronizedList: 迭代器相关方法未加锁 如果需要使用迭代器迭代操作,建议使用SynchronizedList,否则Vector和SynchronizedList一样