深入浅出 CopyOnWriteArrayList

前文

ArrayList 的使用方法

深入理解 ArrayList

深入理解 LinkedList

深入理解 Vector

fail-fast 与 fail-safe 的区别

什么是 CopyOnWriteArrayList?

CopyOnWriteArrayList 我们可以拆分为两个词来理解,一个是 CopyOnWrite,一个是 ArrayList,ArrayList 相信 使用 Java 的朋友都知道。那么什么是 CopyOnWrite 呢?

CopyOnWrite

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源

CopyOnWriteArrayList 的实现原理

线程不安全的 ArrayList

在说 CopyOnWriteArrayList 的实现原理之前,我们先来看下下面这段代码

package juc;

import java.util.*;

/**
 * @author Woo_home
 * @create by 2020/3/7
 */
public class NotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

输出:
在这里插入图片描述
当我们并发地去修改 ArrayList 集合中的内容的时候会抛出 ConcurrentModificationException 异常,因为当我们并发地去修改的时候,ArrayList 会执行 checkForComodification 这个方法

protected transient int modCount = 0;
int expectedModCount = modCount;

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hasNext() 或者 next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedModCount 值,是的话就返回遍历;否则抛出异常,终止遍历

线程安全的 CopyOnWriteArrayList

package juc;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author Woo_home
 * @create by 2020/3/7
 */
public class NotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
            	// 在 list 中添加 UUID 类随机生成的字符串
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 打印
                System.out.println(list);
      		// 启动三十个线程
            },String.valueOf(i)).start();
        }
    }
}

输出:
在这里插入图片描述
可以看到程序是可以正常运行的,那么 CopyOnWriteArrayList 是如何做到数据的一致性的呢?

getArray()

通过源码我们可以发现 CopyOnWriteArrayList 底层是一个 Object 的数组,而我们上面 add() 源码中 getArray() 方法也是返回一个 array 的 Object 数组。而且我们可以发现 array 这个数组是使用 volatile 修饰的,关于 volatile 的说明请看这里 volatile 与 synchronized 详解 ,而且 array 这个数组只提供给 setArray 和 getArray 方法访问

private transient volatile Object[] array;

final Object[] getArray() {
   return array;
}

setArray(Object[] a)

final void setArray(Object[] a) {
    array = a;
}

add(E e) 方法

我们来看下 CopyOnWriteArrayList 的底层是如何实现的

public boolean add(E e) {
	// 初始化 ReentrantLock 
    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;
        // 将array引用指向到新数组
        setArray(newElements);
        return true;
    } finally {
    	// 释放锁
        lock.unlock();
    }
}

在 add() 方法中,有一个 Arrays.copyOf(elements, len + 1); 这段代码的是将原有的集合复制一份出来,然后我们遍历的其实是这个复制的集合而不是原有的集合,这有修改的就不是原有的集合,而是复制的集合,遍历完成之后再复制给新的数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值