• Java并发编程原理与实战三十四:并发容器CopyOnWriteArrayList原理与使用


    1、ArrayList的实现原理是怎样的呢?

    ------》例如:ArrayList本质是实现了一个可变长度的数组。
    假如这个数组的长度为10,调用add方法的时候,下标会移动到下一位,当移动到70%左右的时候。会创建一个新数组,而这个新数组的长度变成2倍或3倍等等。将原来的数据复制到新数组中,新的内容会接着添加下一位。这就是ArrayList的实现原理。
     
    2、CopyOnWriteArrayList的实现原理是怎样的呢?
    ------》
    它的实现原理是在ArrayList的实现原理的基础上实现了增强。get方法是没有线程安全性问题的(读)。而add方法时有线程安全问题的。在看源代码的时候,你就会发现,怎样做到多线程环境下能实现读和写不冲突呢?我们都知道一个普通方法上加了synchronized,在对共享资源(数组)写的时候,是无法对这数组进行读操作。
    但是CopyOnWriteArrayList是可以在写的同时让用户进行读操作。其实现的原理是,目前有两个数组a,b。先将a的内容原封不动地复制到b中,写的时候要加锁,锁的不是a,而是锁b。写完之后,将原本指向a的指针,指向b。再进行读操作,就可以读到两个数据。
     
    3、举例分析
    java并发容器CopyOnWriteArrayList

    顾名思义,当数组有变化时重新建立一个新的数组。其设计是对于线程安全容器Vector使用中出现问题的一种解.

    在Vector容器中,当需要执行复合操作

    代码1

    class Observable 
    {
    private List<Observer> observers=new Vector<Observer>();
    
    public void addObserver(){...}
    public void removeObserver(){...}
    public void notify()
    {
    Iterator<Observer> itr=observers.iterator();
    while(itr.hasNext()){
    Observer observer=itr.next();
    observer.notify();
    }
    return;
    }
    }

    Observable中的notify方法在单线程中的实现是正常的,但在多线程中,由于在notify执行过程中observers数组的内容可能会发生改变,导致遍历失效.即使observers本身是线程安全的也于是无补

    通常解决这个问题,可以使用同步方法或者加锁

    代码2

    class Observable
    {
    private List<Observer> observers=new Vector<Observer>();
     
    public synchronized void addObserver(){...}
     
    public synchronized void removeObserver(){...}
     
    //同步方法
    public synchronized void notify()
    {
    Iterator<Observer> itr=observers.iterator();
    while(itr.hasNext()){
    Observer observer=itr.next();
    observer.notify();
    }
    return;
    }
    }

    这样的解决方案中notify的同步导致另外一个问题,即活跃性问题.当observers中有很多元素或者每一个元素的notify方法调用需要很久时,此方法将长时间持有锁.导致其他任何想修改observers的行为阻塞.最后严重影响程序性能

    CopyOnWriteArrayList即在这种场景下使用.一个需要在多线程中操作,并且频繁遍历.
    其解决了由于长时间锁定整个数组导致的性能问题.
    其解决方案即写时拷贝。

    我们先来贴出使用CopyOnWriteArrayList的Observable代码

    代码3

    class Observable 
    {
    private List<Observer> observers=new CopyOnWriteArrayList<Observer>();
    
    public void addObserver(){...}
    
    public void removeObserver(){...}
    
    public void notify()
    {
    Iterator<Observer> itr=observers.iterator();
    while(itr.hasNext()){
    Observer observer=itr.next();
    observer.notify();
    }
    return;
    }
    }

    Observable的notify方法和代码1相同.但其不会有多线程同时操作的问题.其中的奥秘,通过分析源码可知
    当CopyOnWriteArrayList添加或者删除元素时,其实现为根据当前数组重新建立一个新数组..

     当我们获取CopyOnWriteArrayList的迭代器时,迭代器内保存当前数组的引用.之后如果别的线程改变CopyOnWriteArrayList中元素,则根据CopyOnWriteArrayList的特性,其实并没有改变这个迭代器指向数组的内容.
    4、总结
    在实际运用中,在多线程环境下,如果要对List进行读的操作次数远远比写操作大的话,必须考虑使用CopyOnWriteArrayList。如果写操作比较多的话,可以考虑使用我们的同步容器去做。(为什么写操作比较多的时候要使用我们同步容器去做呢?-----》因为通过源码可以知道,CopyOnWriteArrayList如果写操作比较多的时候,会占用很多的内存
  • 相关阅读:
    vue.js 条件与循环
    vue.js 声明式渲染
    数据库设计范式?
    用户购物车,实现添加商品的功能!
    用户购物车功能的实现。
    初始ajax技术
    SQL语句中 INNER JOIN的用法!
    商城 用户登录、注册、注销,购物车。
    EL和 JSTL? 在JSP中简化 java代码的写法!
    小数点后保留2位小数的正则表达式
  • 原文地址:https://www.cnblogs.com/pony1223/p/9503616.html
Copyright © 2020-2023  润新知