深入理解ConcurrentModificationException
昨天下午到晚上想了一下午的ConcurrentModificationException,fail-fast机制,ArrayList的线程不安全,以及CopyOnWriteList,今天醒来的时候终于梳理清楚,记录一下
我们知道ArrayList是线程不安全的,具体为什么推荐看这篇博客,写的很清楚,我就不再赘述:https://blog.****.net/toocruel/article/details/82753615
我们知道,我们在用ArrayList的迭代器的时候后如果又对ArrayList进行修改,那么会报出一个ConcurrentModificationException异常,那么为什么需要有这个异常?(注意这里讨论的是为什么需要有,而不是这个异常是怎么出现的,出现的原因是java的fail-fast机制,modCount数值改变)
答案是为了维护遍历结果的正确性
我们来看下面这个例子:
我们在遍历的时候进行了remove操作,这时会出现ConcurrentModificationException
那么我们可以想一下"假设"不抛出这个异常会怎样,我把JDK的源码给拷贝过来,自己把抛出ConcurrentModificationException的地方给删去:
这样就不会抛出ConcurrentModificationException异常了,我们现在来运行一下刚才的代码:
这时已经不会抛出异常了,但我们读到的却是从不想要的错误数据:数据"4"莫名其妙的丢失了,里面的原理很简单,相信大家都能想到,在程序设计中,正确性是软件的根基,所以是必须要进行维护的,所以ConcurrentModificationException正是为了维护数据正确性的,但是请注意,JDK文档中已经说明java的fail-fast机制尽可能地抛出ConcurrentModificationException异常,但并不能保证.
那么,ArrayList有这样的缺点,有没有现成的解决方案来解决它呢?答案是有的,使用java.util.concurrent包下的CopyOnWriteList就可以解决这个问题,CopyOnWriteList是线程安全的,昨天也研究了一下这个List,它的原理是读写分离,在对数组进行修改(add,remove,clear)时就先加锁,然后拷贝原来的数据,然后再在新的数据上面修改,然后释放锁,将原来的数组指向新的地址.
那么,为什么CopyOnWriteList在使用迭代器的时候难道就不会出现上面ArrayList的情况吗?数据就一定正确吗?
答案是是的,不会出现错误情况,并且数据一定正确(注意是正确,不是准确)
我们来看下面这段代码:
运行结果是这样的:
我们可以看出,虽然在迭代时对CopyOnWriteList进行了更改,但迭代器不会反应最新的变化,也就是迭代器只认真地对创建时的那一份数据进行迭代,新有的更改全都不知道,所以如果不更新迭代器,所得到的数据是旧的数据,这也就是为什么说得到的数据是正确的,而不是准确(实时)的.
说到这里,与ConcurrentModificationException相关联的还有一个java的fail-fast机制,我们去搜索的时候经常会出现这样一段话;
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
但其实我认为这句话一点都不准确,在我进行实际测试的时候,多个线程对ArrayList进行操作,即使最终结果是错误的,也没有抛出任何异常,相反在单线程内迭代的同时进行修改结构就可以造成抛出异常,所以我更喜欢的一个说法是:
fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。
就先写到这里吧~