• 线程安全的CopyOnWriteArrayList介绍


    证明CopyOnWriteArrayList是线程安全的

    先写一段代码证明CopyOnWriteArrayList确实是线程安全的。

    ReadThread.java

    import java.util.List;

    public class ReadThread implements Runnable {
    private List<Integer> list;

    public ReadThread(List<Integer> list) {
    this.list = list;
    }

    @Override
    public void run() {
    for (Integer ele : list) {
    System.out.println("ReadThread:"+ele);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    WriteThread.java

    import java.util.List;

    public class WriteThread implements Runnable {
    private List<Integer> list;

    public WriteThread(List<Integer> list) {
    this.list = list;
    }

    @Override
    public void run() {
    this.list.add(9);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    TestCopyOnWriteArrayList.java

    import java.util.Arrays;
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    public class TestCopyOnWriteArrayList {

    private void test() {
    //1、初始化CopyOnWriteArrayList
    List<Integer> tempList = Arrays.asList(new Integer [] {1,2});
    CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(tempList);


    //2、模拟多线程对list进行读和写
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    executorService.execute(new ReadThread(copyList));
    executorService.execute(new WriteThread(copyList));
    executorService.execute(new WriteThread(copyList));
    executorService.execute(new WriteThread(copyList));
    executorService.execute(new ReadThread(copyList));
    executorService.execute(new WriteThread(copyList));
    executorService.execute(new ReadThread(copyList));
    executorService.execute(new WriteThread(copyList));

    System.out.println("copyList size:"+copyList.size());
    }


    public static void main(String[] args) {
    new TestCopyOnWriteArrayList().test();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    运行上面的代码,没有报出

    java.util.ConcurrentModificationException
    1
    说明了CopyOnWriteArrayList并发多线程的环境下,仍然能很好的工作。

    CopyOnWriteArrayList如何做到线程安全的

    CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。

    当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。

    当元素在新数组添加成功后,将array这个引用指向新数组。

    CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。
    这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。

    CopyOnWriteArrayList的add操作的源代码如下:

    public boolean add(E e) {
    //1、先加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    Object[] elements = getArray();
    int len = elements.length;
    //2、拷贝数组
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    //3、将元素加入到新数组中
    newElements[len] = e;
    //4、将array引用指向到新数组
    setArray(newElements);
    return true;
    } finally {
    //5、解锁
    lock.unlock();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
    1、如果写操作未完成,那么直接读取原数组的数据;
    2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
    3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。

    可见,CopyOnWriteArrayList的读操作是可以不用加锁的。

    CopyOnWriteArrayList的使用场景

    通过上面的分析,CopyOnWriteArrayList 有几个缺点:
    1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc

    2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

    CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用
    因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

    CopyOnWriteArrayList透露的思想

    如上面的分析CopyOnWriteArrayList表达的一些思想:
    1、读写分离,读和写分开
    2、最终一致性
    3、使用另外开辟空间的思路,来解决并发冲突

    参考的文章

    JAVA中的COPYONWRITE容器
    ---------------------
    作者:Sam哥哥
    来源:CSDN
    原文:https://blog.csdn.net/linsongbin1/article/details/54581787
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    如何去掉myeclipse的web项目启动时出现的 CodeLive Panel
    day35_Spring学习回顾_03
    在CentOS/RHEL上设置SSH免密码登录
    超全Linux备份工具集合,满足你的所有需要!
    使用Gnupg对Linux系统中的文件进行加密
    Linux压缩那些事儿
    或许是 Nginx 上配置 HTTP2 最实在的教程了
    Systemd 三部曲 之 PHP7
    Linux kernel模块管理相关详解
    搜狐视频Redis私有云平台CacheCloud
  • 原文地址:https://www.cnblogs.com/GarfieldEr007/p/10101758.html
Copyright © 2020-2023  润新知