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


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

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

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

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



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



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

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

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



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



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

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





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

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

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

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



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



    成功解决:



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

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



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


    总结

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

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

  • 相关阅读:
    AO中的GraphicsLayer---------元素的容器
    Spring中基于Java的配置@Configuration和@Bean用法
    spring注解开发AnnotationConfigApplicationContext的使用
    java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getClassLoader")
    java.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory的解决办法
    Multiple markers at this line @Override的解决方法
    springmvc-mvc:resource标签使用
    SpringMVC <mvc:view-controller path=""/>标签
    深入理解Spring MVC 思想
    解决 Eclipse 导入项目后 Maven Dependencies missing jar 问题
  • 原文地址:https://www.cnblogs.com/Absofuckinglutely/p/13274299.html
Copyright © 2020-2023  润新知