当前位置:   article > 正文

Java并发集合 - CopyOnWriteArrayList详解_java copyonwritearraylist

java copyonwritearraylist

1. 什么是CopyOnWriteArrayList?

CopyOnWriteArrayList是java.util.concurrent包中提供的一个线程安全的ArrayList。它通过一种称为“写时复制”(Copy-On-Write)的方法来实现线程安全。简而言之,每当我们尝试修改这个列表(如添加、删除元素)时,它实际上并不直接在当前的列表上进行修改,而是先将当前列表复制一份,然后在这个副本上进行修改,修改完毕后再将原列表的引用指向新修改过的列表。这种机制确保了在读操作期间数据的不变性,非常适合读多写少的场景。

2. CopyOnWriteArrayList源码解析

2.1 类继承关系

  • 实现List接口:List接口定义了对列表的基本操作。
  • 实现RandomAccess接口:表示可以随机访问,数组具有随机访问的属性。
  • 实现Cloneable接口:表示可克隆。
  • 实现了Serializable接口:表示可序列化。
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
  • 1
  • 2

2.2 类属性

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 版本序列号
    private static final long serialVersionUID = 8673264195747942595L;
    // 可重入锁定
    final transient ReentrantLock lock = new ReentrantLock();
    //  对象数组,用于存放元素 CopyOnWriteArrayList 核心,使用volatile保证array的可见性和有序性。
    private transient volatile Object[] array;
    // Unsafe 类
    private static final sun.misc.Unsafe UNSAFE;
    // lock域的内存偏移量
    private static final long lockOffset;
     static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = CopyOnWriteArrayList.class;
            lockOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("lock"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2.3 核心函数

// 向列表末尾添加一个元素
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;
        // 设置数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

// 此函数是移除列表上的元素-返回旧值
public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 获取旧值
        E oldValue = get(elements, index);
        // 获取需要移动的元素个数
        int numMoved = len - index - 1;
        // 需要移动的个数为0
        if (numMoved == 0)
            // 直接复制一个
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 新生数组
            Object[] newElements = new Object[len - 1];
            // 复制index之前的数据
            System.arraycopy(elements, 0, newElements, 0, index);
            // 复制index之后的数据
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            // 设置数组
            setArray(newElements);
        }
        // 返回旧值
        return oldValue;
    } finally {
        lock.unlock();
    }
}
// 此函数用于用指定的元素替代此列表指定位置上的元素,也是基于数组的复制来实现的。
public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获取数组
        Object[] elements = getArray();
        // 获取对应下标中元素的数据
        E oldValue = get(elements, index);
        // 判断旧数据与新数据是否一致
        if (oldValue != element) {
            // 获取数组长度
            int len = elements.length;
            // 复制数组
            Object[] newElements = Arrays.copyOf(elements, len);
            // 更新对应数组下标的数据
            newElements[index] = element;
            // 设置数组
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            // 设置数组
            setArray(elements);
        }
        // 返回旧值
        return oldValue;
    } finally {
        lock.unlock();
    }
}
  • 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
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

2.4 类构造器

// 默认构造器
public CopyOnWriteArrayList() {
    // 设置数组
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class) // 类型相同
        // 获取c集合的数组
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else { // 类型不相同
        // 将c集合转化为数组并赋值给elements
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class) // elements类型不为Object[]类型
            // 将elements数组转化为Object[]类型的数组
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    // 设置数组
    setArray(elements);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.5 内部类

CopyOnWriteArrayList 的内部类 COWIterator 是一个实现了 ListIterator 接口的迭代器类,专门为 CopyOnWriteArrayList 定制。它提供了一种安全的方式来遍历 CopyOnWriteArrayList,即使在多线程环境中也不会抛出 ConcurrentModificationException。我们来逐个分析这个内部类的主要部分。

static final class COWIterator<E> implements ListIterator<E> {
    // 数组快照,所有的迭代操作都是在这个数组快照上进行的,保证了在迭代过程中,即使原列表被修改,迭代器仍然可以保持一致的视图。后续在使用示例中会表示出来。
    private final Object[] snapshot;
    // 这个字段是一个指针,指向下一个要返回的元素的索引
    private int cursor;
    // 构造器
    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }
	// 判断是否还有下一个元素
    public boolean hasNext() {
        return cursor < snapshot.length;
    }
	// 判断是否还有上一个元素
    public boolean hasPrevious() {
        return cursor > 0;
    }
	// 用于获取下一个元素,并更新cursor的位置
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
	// 用于获取下一个元素,并更新cursor的位置
    @SuppressWarnings("unchecked")
    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }
    // 返回下一个元素索引
    public int nextIndex() {
        return cursor;
    }
    // 返回上一个元素索引
    public int previousIndex() {
        return cursor-1;
    }

    // 不支持操作
    public void remove() {
        throw new UnsupportedOperationException();
    }

    // 不支持操作
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    // 不支持操作
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

    // 允许使用 lambda 表达式或方法引用来迭代剩余的元素
    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        Object[] elements = snapshot;
        final int size = elements.length;
        for (int i = cursor; i < size; i++) {
            @SuppressWarnings("unchecked") E e = (E) elements[i];
            action.accept(e);
        }
        cursor = size;
    }
}
  • 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
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

3. CopyOnWriteArrayList的使用示例

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
        // 添加元素
        cowList.add("Java");
        cowList.add("Python");
        cowList.add("C++");

        // 创建线程来修改列表
        Thread threadOne = new Thread(() -> {
            cowList.add("JavaScript");
            cowList.remove("Python");
        });
        
        // 启动线程
        threadOne.start();

        // 同时进行迭代操作
        Iterator<String> iterator = cowList.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            try {
                Thread.sleep(100); // 延迟以模拟读取操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("=============更新之后=============");
        cowList.forEach(System.out::println);
    }
}
  • 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

在上述示例中,我们创建了一个CopyOnWriteArrayList并在一个线程中对其进行了修改。同时,主线程中遍历了列表。由于CopyOnWriteArrayList的特性,即使列表在遍历过程中被修改,迭代器也不会抛出ConcurrentModificationException异常,因为迭代器是在列表被修改之前创建的。

4. CopyOnWriteArrayList的使用场景

  1. 读多写少的应用场景:如果应用程序中读取操作的频率远高于写入操作,CopyOnWriteArrayList将非常适用。
  2. 迭代器的一致性视图:当需要确保在迭代过程中集合不会被并发修改时,CopyOnWriteArrayList提供了一种简单有效的解决方案。
  3. 避免锁的开销:在某些场景下,使用传统的同步集合(如VectorCollections.synchronizedList)可能会因为频繁的锁操作而导致性能问题。CopyOnWriteArrayList通过减少锁的使用提供了一种高效的替代方案。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/821940
推荐阅读
相关标签
  

闽ICP备14008679号