• 并发编程学习笔记(二十三、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 }
  • 相关阅读:
    js获取url参数
    css3 text-shadow字体阴影讲解
    css3 box-shadow阴影(外阴影与外发光)讲解
    【leetcode刷题笔记】Palindrome Partitioning II
    【leetcode刷题笔记】Minimum Window Substring
    【leetcode刷题笔记】Interleaving String
    【leetcode刷题笔记】Wildcard Matching
    【leetcode刷题笔记】Regular Expression Matching
    【leetcode刷题笔记】Insert Interval
    【leetcode刷题笔记】Multiply Strings
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/13219532.html
Copyright © 2020-2023  润新知