栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC常用类CopyOnWriteArrayList

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC常用类CopyOnWriteArrayList

文章目录
  • 前言
  • 一、为什么要引入CopyOnWriteArrayList?
  • 二、CopyOnWriteArrayList
  • 总结


前言

JUC有一个常用类CopyOnWriteArrayList,非常适合在多读少写的场景,CopyOnWriteArraySet的实现也是基于 CopyOnWriteArrayList(不能存储重复数据),所以我们直接学习CopyOnWriteArrayList即可。


一、为什么要引入CopyOnWriteArrayList?

1.ArraayList 是线程不安全的(读和写都不加锁),在高并发情况下可能会出现问题。

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
 public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

2.Vector是线程安全的(读和写都会加锁),但是在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修 改原有的数据,因此如果每次读取都进行加锁操作,其实是一种资源浪费。

  public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
   }
    
  public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        return elementData(index);
    }

所以,多读写少的场景 ,JDK 中提供了CopyOnWriteArrayList类。
目的是将读取的性能发挥到极致,读不加锁,写也不会阻塞读取操作,只有写写之间互斥,读操作的性能得到大幅度提升。


二、CopyOnWriteArrayList

CopyOnWrite翻译成中文,大概就是“写数据的时候利用拷贝的副本来执行”。

CopyOnWriteArrayList类的所有可变操作(add,set 等等)都是通过创建底层数组的新副本来实现的,其步骤如下。
1.当 List 需要被修改的时候,并不直接修改原有数组对象,而是对原有数据进行一次拷贝,将修改的内容写入副本中。
2.写完之后,再将修改完的副本替换成原来的数据。

总结:这个过程中实际上都是在操作一个副本而已,这样就可以保证写操作不会影响读操作。

在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。
1.CopyOnWriteArrayList中add方法的实现,可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

    // 这个数组是核心的,因为用volatile修饰了
    // 只要把最新的数组对他赋值,其他线程立马可以看到最新的数组,保证了可见性
    private transient volatile Object[] array;
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 对数组拷贝一个副本出来
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 对副本数组进行修改,比如在里面加入一个元素
            newElements[len] = e;
            // 然后把副本数组赋值给volatile修饰的变量
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

2.读的时候不需要加锁,直接读取,将读取的性能发挥到极致。

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

注意:如果读的时候有线程正在向CopyOnWriteArrayList添加数据。读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。


总结

如果用了CopyOnWriteArrayList,就是用空间换时间,更新的时候基于副本更新,然后最后用volatile变量来赋值保证可见性,更新的时候对读线程没有任何的影响。写操作过多的情况下不能使用,因为写操作的时候牵扯到数据拷贝的情况,实际上是很耗费资源。


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/952777.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号