并发编程学习笔记(二十三、CopyOnWriteArrayList源码分析)

目录:

  • CopyOnWriteArrayList是什么
  • 为什么要有CopyOnWriteArrayList
  • 如何使用CopyOnWriteArrayList
  • CopyOnWriteArrayList源码分析

CopyOnWriteArrayList是什么

CopyOnWriteArrayList是一个写入时复制的集合,与其说它是一种新的数据结构还不如说它是一种设计思想

首先从它的名字来看,猜测它是ArrayList一种扩展,是在写入时复制到另一个容器的扩展,那这种扩展有什么用呢,往下看。

为什么要有CopyOnWriteArrayList

参考:https://baijiahao.baidu.com/s?id=1666117483794506320&wfr=spider&for=pc

——————————————————————————————————————————————————————————————————————

存在即合理,我们之前说CopyOnWriteArrayList是ArrayList的一种扩展,那我们这里就来讨论下,它扩展的到底是什么。

首先我们知道传统的ArrayList是非线程安全的,在多个线程读写的情况下会出现ConcurrentModificationException的异常,如果我们操作集合的场景是要求线程安全的话那就得使用Vector或是自己包装下ArrayList

当然这种使用方式会影响读写效率的,因为他们都是采用独占锁实现的,同一时刻只能有一个线程能操作集合。

而CopyOnWriteArrayList正是为了解决这一问题。

——————————————————————————————————————————————————————————————————————

1、独占锁效率低:采用读写分离思想解决:

既然独占锁的效率低下,那我们可以换一种方式,采用读写分离式的思想将读操作和写操作进行分开即可。

读操作不加锁,所有线程都不会阻塞;写操作加锁,线程会阻塞。

2、写线程获取到锁,其他线程包括读线程阻塞:

但是这时候又出现了另外一个问题了:写线程获取到锁之后,其他的读线程会陷入阻塞

3、复制思想:解决问题2:

这咋办呢?我们可以再转化一下思想:

当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器

这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写回内存其他的线程就会读到了脏数据。这就是CopyOnWriteArrayList的思想和原理,就是拷贝一份写;所以使用条件也很局限,那就是在读多写少的情况下比较好

如何使用CopyOnWriteArrayList

使用方式和ArrayList类似,因为他们都是List的子类。

 1 public static void main(String[] args) throws InterruptedException {
 2     // 塞10个数据用于测试
 3     CopyOnWriteArrayList<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
 4     for (int i = 0; i < 10; i++) {
 5         copyOnWriteArrayList.add(i);
 6     }
 7     
 8     // 定时打印集合元素
 9     new Thread(() -> {
10         for (int i = 0; i < copyOnWriteArrayList.size(); i++) {
11             try {
12                 Thread.sleep(1000);
13             }
14             catch (InterruptedException e) {
15                 e.printStackTrace();
16             }
17             System.out.println(Thread.currentThread().getName() + "输出元素:" + copyOnWriteArrayList.get(i));
18         }
19     }).start();
20     // 定时删除集合元素
21     new Thread(() -> {
22         for (int i = 0; i < copyOnWriteArrayList.size(); i++) {
23             try {
24                 Thread.sleep(800);
25             }
26             catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29             if (copyOnWriteArrayList.get(i) % 2 == 0) {
30                 System.out.println(Thread.currentThread().getName() + "删除元素:" + copyOnWriteArrayList.remove(i));
31             }
32         }
33     }).start();
34 
35     Thread.sleep(10000);
36     for (Integer integer : copyOnWriteArrayList) {
37         System.out.println("CopyOnWriteArrayList剩余元素:" + integer);
38     }
39 }

CopyOnWriteArrayList源码分析

前面我们说到CopyOnWriteArrayList并不算是一种新的数据结构,而是一种思想,其核心思想就是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。

根据上面所说的我们来看下CopyOnWriteArrayList添加元素的add方法:

 1 public boolean add(E e) {
 2     final ReentrantLock lock = this.lock;
 3     lock.lock();
 4     try {
 5         Object[] elements = getArray();
 6         int len = elements.length;
 7         Object[] newElements = Arrays.copyOf(elements, len + 1);
 8         newElements[len] = e;
 9         setArray(newElements);
10         return true;
11     } finally {
12         lock.unlock();
13     }
14 }

源码很简单,和上面的所说的实现一样,首先是我们所说的为了线程安全而加的重入锁,然后我们拿到CopyOnWriteArrayList中的数据源array,之后复制一份新的数组并将传入的值e添加到新数组,最后重新设置CopyOnWriteArrayList中的数据源为新数组。

以及set、remove方法均是如此,你可以对比着看下。

 1 public E set(int index, E element) {
 2     final ReentrantLock lock = this.lock;
 3     lock.lock();
 4     try {
 5         Object[] elements = getArray();
 6         E oldValue = get(elements, index);
 7 
 8         if (oldValue != element) {
 9             int len = elements.length;
10             Object[] newElements = Arrays.copyOf(elements, len);
11             newElements[index] = element;
12             setArray(newElements);
13         } else {
14             // Not quite a no-op; ensures volatile write semantics
15             setArray(elements);
16         }
17         return oldValue;
18     } finally {
19         lock.unlock();
20     }
21 }
 1 public E remove(int index) {
 2     final ReentrantLock lock = this.lock;
 3     lock.lock();
 4     try {
 5         Object[] elements = getArray();
 6         int len = elements.length;
 7         E oldValue = get(elements, index);
 8         int numMoved = len - index - 1;
 9         if (numMoved == 0)
10             setArray(Arrays.copyOf(elements, len - 1));
11         else {
12             Object[] newElements = new Object[len - 1];
13             System.arraycopy(elements, 0, newElements, 0, index);
14             System.arraycopy(elements, index + 1, newElements, index,
15                              numMoved);
16             setArray(newElements);
17         }
18         return oldValue;
19     } finally {
20         lock.unlock();
21     }
22 }
原文地址:https://www.cnblogs.com/bzfsdr/p/13219532.html