• CopyOnWriteArrayList线程安全分析


    CopyOnWriteArrayList是开发过程中常用的一种并发容器,多用于读多写少的并发场景。但是CopyOnWriteArrayList真的能做到完全的线程安全吗? 答案是并不能。

    一、CopyOnWriteArrayList原理

      我们可以看出当我们向容器添加或删除元素的时候,不直接往当前容器添加删除,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加删除元素,添加删除完元素之后,再将原容器的引用指向新的容器,整个过程加锁,保证了写的线程安全。

     1     public boolean add(E e) {
     2         synchronized (lock) {
     3             Object[] elements = getArray();
     4             int len = elements.length;
     5             Object[] newElements = Arrays.copyOf(elements, len + 1);
     6             newElements[len] = e;
     7             setArray(newElements);
     8             return true;
     9         }
    10     }
    11 
    12     public E remove(int index) {
    13         synchronized (lock) {
    14             Object[] elements = getArray();
    15             int len = elements.length;
    16             E oldValue = get(elements, index);
    17             int numMoved = len - index - 1;
    18             if (numMoved == 0)
    19                 setArray(Arrays.copyOf(elements, len - 1));
    20             else {
    21                 Object[] newElements = new Object[len - 1];
    22                 System.arraycopy(elements, 0, newElements, 0, index);
    23                 System.arraycopy(elements, index + 1, newElements, index,
    24                                  numMoved);
    25                 setArray(newElements);
    26             }
    27             return oldValue;
    28         }
    29     }

      而因为写操作的时候不会对当前容器做任何处理,所以我们可以对容器进行并发的读,而不需要加锁,也就是读写分离。

    1     public E get(int index) {
    2         return get(getArray(), index);
    3     }

      一般来讲我们使用时,会用一个线程向容器中添加元素,一个线程来读取元素,而读取的操作往往更加频繁。写操作加锁保证了线程安全,读写分离保证了读操作的效率,简直完美。

    二、数组越界

      但想象一下如果这时候有第三个线程进行删除元素操作,读线程去读取容器中最后一个元素,读之前的时候容器大小为i,当去读的时候删除线程突然删除了一个元素,这个时候容器大小变为了i-1,读线程仍然去读取第i个元素,这时候就会发生数组越界。 

      测试一下,首先向CopyOnWriteArrayList里面塞10000个测试数据,启动两个线程,一个不断的删除元素,一个不断的读取容器中最后一个数据。

     1     public void test(){
     2         for(int i = 0; i<10000; i++){
     3             list.add("string" + i);
     4         }
     5 
     6         new Thread(new Runnable() {
     7             @Override
     8             public void run() {
     9                 while (true) {
    10                     if (list.size() > 0) {
    11                         String content = list.get(list.size() - 1);
    12                     }else {
    13                         break;
    14                     }
    15                 }
    16             }
    17         }).start();
    18 
    19         new Thread(new Runnable() {
    20             @Override
    21             public void run() {
    22                 while (true) {
    23                     if(list.size() <= 0){
    24                         break;
    25                     }
    26                     list.remove(0);
    27                     try {
    28                         Thread.sleep(10);
    29                     } catch (InterruptedException e) {
    30                         e.printStackTrace();
    31                     }
    32                 }
    33             }
    34         }).start();
    35     }

      运行,可以看出删除到第7个元素的时候就发生了数组越界:

     

      从上可以看出CopyOnWriteArrayList并不是完全意义上的线程安全,如果涉及到remove操作,一定要谨慎处理。

  • 相关阅读:
    mysql 中只能使用 localhost 登录,用ip不能登陆
    在springboot 和 mybatis 项目中想要显示sql 语句进行调试
    从一张表数据导入到另一张表
    mysql 中 delete 子查询的限制
    配置eureka 老是报错connected time out 或者 refused connected
    Linux-TCP 出现 RST 的几种情况
    MySQL-优化之 index merge(索引合并)
    Python-Mac 安装 PyQt4
    PHP-PHP-FPM的max_children一些误区
    Linux-磁盘及网络IO工作方式解析
  • 原文地址:https://www.cnblogs.com/baichunyu/p/12964819.html
Copyright © 2020-2023  润新知