ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

并发集合_CopyOnWriteArrayList原理分析

2021-07-20 13:02:56  阅读:204  来源: 互联网

标签:index 读取 修改 lock CopyOnWriteArrayList 并发 线程 集合


在Java 集合框架中,我们介绍了List集合中最常用的子类ArrayList和LinkedList,但是它们都不保证多线程安全。如果多个线程同时读取和修改数据,就会产生冲突。

要实现多线程安全,可以使用同步集合Vector以及使用Collections类中synchronizedXXX系列方法。它们都是使用synchronized同步锁,保证同一时间只有一个线程能读取或修改集合。

其实还有一种更加高效的方法,CopyOnWrite(只有在修改时才复制)。

CopyOnWrite方式

它的核心思想就是当我们对集合进行读取时,不做任何锁的控制,可以多线程并发读取,但是我们任何对集合进行修改的操作,都要加锁控制,保证同一时间只有一个线程,进行下面三个操作:(注意这里说的)

  1. 先用老集合copy出一份新集合.
  2. 然后在新集合上做修改.
  3. 最后用新集合直接替换老集合.

注意:这里说的集合,就是集合中的数组array对象。

因为我们是在新集合中进行修改,所以不会影响正在读取数据的老集合,然后将新集合替换老集合,就能读取到最新修改的数据。

所以这里也就暴露了CopyOnWrite这种方式的缺点,就是我们修改了集合数据,可能不会立即读取到这个新数据,得到的还是老数据。
例如我们一个线程调用了set(int index, E element)替换了集合index位置的数据,然后另一个线程调用get(index),可能得不到那个线程对集合的修改。

CopyOnWriteArrayList集合

CopyOnWriteArrayList集合是一种CopyOnWrite的一种实现,根据CopyOnWrite方式介绍。所以我们知道CopyOnWriteArrayList集合中所有读取的方法都是不加锁的,而修改的方法是加锁的。

重要属性

/** 通过ReentrantLock来实现独占锁的 */
final transient ReentrantLock lock = new ReentrantLock();

/** 用来储存集合中数据,使用volatile关键字修饰,保证可见性和有序性 */
private transient volatile Object[] array;

CopyOnWriteArrayList集合是使用lock来进行加锁操作的。

读取方法

 // 不需要加同步锁
public int size() {
    return getArray().length;
}
// 不需要加同步锁
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
    return (E) a[index];
}

// 不需要加同步锁
public E get(int index) {
    return get(getArray(), index);
}

这里选取了几个读取的方法,它们都不没有加同步锁。所以正在修改或者等待修改的东西,是读取不到的,以此会有延时性。

修改方法

当进行修改集合的时候,我们要保证三个操作是多线程同步的,即使用老数组创建新数组,修改新数组,将新数组替换老数组。这个三个操作必须同一时间只有一个线程操作。

不然就会出现这样一种情况:

  1. 多个线程一起调用getArray方法,得到老数组array。
  2. 然后其中一个线程操作完成之后,首先调用setArray方法,修改了共享变量array,
  3. 之后又用另一个线程操作完成调用setArray方法,修改了共享变量array,
  4. 这个时候你就会发现,另一个线程对集合的修改不是在前一个修改之上的,它会把上个线程的修改覆盖,产生多线程冲突。
public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 使用lock锁,保证修改共享变量array的线程安全。
    lock.lock();
    try {
        // 获取老数组elements
        Object[] elements = getArray();
        // 得到原来的值
        E oldValue = get(elements, index);

        // 有改变才修改
        if (oldValue != element) {
            int len = elements.length;
            // 用老的数组创建一个新数组
            Object[] newElements = Arrays.copyOf(elements, len);
            // 更新index位置的值
            newElements[index] = element;
            // 设置新数组
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            // 这个并不是无用语句,确保volatile变量的重新写入
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

概括总结

CopyOnWriteArrayList集合可以解决多线程操作集合的并发问题,而且它的读取操作非常快,而修改操作将对较慢,因为使用的lock锁,同一时间只有一个线程,能够进行修改操作,其他线程必须等待。 它唯一的缺点就是,一个线程对CopyOnWriteArrayList集合进行修改,另一线程并不能立即读取到。当然对同一线程来说,就没有这个问题的。 另外还有一个CopyOnWriteArraySet集合,它的内部就是使用CopyOnWriteArrayList集合实现的。  

 

标签:index,读取,修改,lock,CopyOnWriteArrayList,并发,线程,集合
来源: https://www.cnblogs.com/xfeiyun/p/15034315.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有