zoukankan      html  css  js  c++  java
  • CopyOnWriteArrayList真的完全线程安全吗

    我之前书上看到的说法是:Vector是相对线程安全,CopyOnWriteArrayList是绝对线程安全

    这种说法其实有些问题,CopyOnWriteArrayList在某些场景下还是会报错的

    CopyOnWriteArrayList解决了:1.多线程一边读一边写。2.多线程迭代时修改抛出并发修改异常问题

    CopyOnWriteArrayList不能做到完全的线程安全参见下面的例子

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

    CopyOnWriteArrayList原理

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

        public boolean add(E e) {
            synchronized (lock) {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            }
        }
    
        public E remove(int index) {
            synchronized (lock) {
                Object[] elements = getArray();
                int len = elements.length;
                E oldValue = get(elements, index);
                int numMoved = len - index - 1;
                if (numMoved == 0)
                    setArray(Arrays.copyOf(elements, len - 1));
                else {
                    Object[] newElements = new Object[len - 1];
                    System.arraycopy(elements, 0, newElements, 0, index);
                    System.arraycopy(elements, index + 1, newElements, index,
                                     numMoved);
                    setArray(newElements);
                }
                return oldValue;
            }
        }

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

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

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

    数组越界

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

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

        public void test(){
            for(int i = 0; i<10000; i++){
                list.add("string" + i);
            }
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if (list.size() > 0) {
                            String content = list.get(list.size() - 1);
                        }else {
                            break;
                        }
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if(list.size() <= 0){
                            break;
                        }
                        list.remove(0);
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }

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

     
     

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




    链接:https://www.jianshu.com/p/fc0ee3aaf2df
  • 相关阅读:
    Webpack配置
    闭包函数
    Vue2.0(一) 新手搭建环境
    用python编写一个合格的ftp程序,思路是怎样的?
    项目流程规范
    python: 基本数据类型 与 内置函数 知识整理
    前端知识 备忘录
    架构的演化路线
    深入理解并使用python的模块与包
    jquery 知识整理
  • 原文地址:https://www.cnblogs.com/twoheads/p/10688196.html
Copyright © 2011-2022 走看看