ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入

对于 ArrayList 类我们知道,它是线程不安全的。但 ArrayList 类为了防止在多线程下,对同一个对象数据进行 读写 或 写写 并发导致的问题,便使得在创建一个它的对象时,会生成一个属于该对象的 int 类型变量:modCount,用来记录当前对象被修改过的次数(add添加操作、remove删除操作等)

让我们先来观察下 ArrayList 类在使用上所产生的异常错误

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第1张图片

按上述代码运行下去,在遍历到第二个 "bb" 时便会产生错误,抛出 ConcurrentModificationException 异常,这个异常是指 “不可同时修改异常” 。
按理我们只是单线程执行,为什么会抛出这个异常呢?

  • 首先,我们先看一下 ArrayList 所重写的 add() 方法:

    ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第2张图片

    再者,我们查看一下 add() 调用的 ensureCapacityInternal() 方法 以及 其中调用的 ensureExplicitCapacity() 方法:

    ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第3张图片

    进而,我们发现到,当 ArrayList 执行 add() 操作时,会使得当前对象的 modCount 自增 1 ,表示修改次数加 1 ,在 remove() 方法里也有同样的操作。

  • 其次,这种 foreach 语法遍历集合元素的时候,其本质是通迭代器来实现的(在 ArrayList 类里封装着一个叫 Itr 的内部类,其实现了 Iterator 接口。也封装着一个叫 ListItr 的内部类,其继承于 Itr ,实现于 ListIterator 接口)

    而在当前集合的迭代器对象被创建的时候,会含有一个叫 expectedModCount 的 int 类型变量,初始化的值为当前对象的 modcount 的值。

    ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第4张图片

    我们知道,在迭代器对本集合元素遍历的时候,一般都是先通过 hasnext() 来判断是否含有下一个元素,之后通过 next() 来获取下一个元素的引用。而在上面源码能看到 next() 方法中,在一开始就调用了一个名为 checkForComodification() 的方法,该方法源码如下:

    ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第5张图片

    重点的地方来了:它会抛出一个 ConcurrentModificationException 异常,这个异常是由于当前 ArrayList 对象的 modCount 值 与 当前迭代器对象的 expectedModCount 值不相同所导致的。

回到最开始的代码,我们可以发现,在使用 foreach 语法遍历集合元素时,expectedModCount 值就已经被创建了。当第一个 add() 执行后,modCount 值加 1,在遍历到第二个集合元素时,触发了 next() 方法,抛出了 ConcurrentModificationException 异常。控制台中可以发现,只输出了一次 list 元素。

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第6张图片

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第7张图片

这种虽然不是在多线程下,却在遍历(读)的同时写数据,看上去在 ArrayList 类中是不被允许的。

解决方案一:不使用 ArrayList 类的 remove() 方法 或 add() 方法,而改为使用 Itr 内部类中的 remove() 方法(实现于 Iterator 接口)或 ListItr 内部类中的 add() 方法(继承了 Itr 内部类,实现于 ListIterator 接口)

如果只是为了解决上述问题,而非适用于多线程情况的话,可以采用该方法去避免产生 ConcurrentModificationException 异常。
可以解决的原因是:Itr 内部类所重写的 remove() 或 ListItr 内部类所重写的 add() 方法中,在修改 modCount 值之后,会再使得 expectedModCount 的值与 modCount 的值相等。

Itr 内部类中的 remove() 方法:

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第8张图片

以及 ListItr 内部类中的 add() 方法:

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第9张图片

成功解决:

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第10张图片

解决方案二:使用 CopyOnWriteArrayList 类来代替 ArrayList 类

CopyOnWriteArrayList 类:
1、它是线程安全的,因为在其对象创建时,会生成 ReentrantLock ,且其方法内部伴有加锁、解锁过程。
2、在写操作方法的内部(add() 方法 以及 remove() 方法),会有 CopyOnWrite 操作,即 COW 技术。在写操作集合之前,先复制一份集合内容,在新的集合上去执行写操作,而此时即便有读操作接入,也可以直接读取原集合的数据。当写操作执行完成后,会更改原集合的引用,指向于这个已经被当次写操作完成后的新集合,从而达到 “读写分离” 的效果。

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第11张图片

正如上面所说,ArrayList 类为了使得读写操作不冲突,而加入了 modCount 以及 expectedModCount 。而 CopyOnWriteArrayList 类由于其两大特性,保证了 读写(特性2)以及 写写(特性1)的安全性。直接使用 CopyOnWriteArrayList 类 ,那么一开始所提到的异常问题就不攻自破了。

ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入_第12张图片

总结

CopyOnWriteArrayList 类是线程安全的,由于源码造成的特性,导致其能很好地解决多线程下的并发问题。但是由于在写操作时可能会频繁地复制集合,从而导致时间复杂度上升。但由于读写是分离的状态(因为操作的是两个不同引用的集合),从而造成“读写分离”却不用上锁,也缩短了时间的开销。

从而得知,它适用于高并发,且适用于多读少写的场景,比如缓存。

你可能感兴趣的:(ArrayList类的不安全性 以及 CopyOnWriteArrayList 类的引入)