看图学源码之 CopyOnWriteArrayList 源码分析

基本简介:

是Java中的一个线程安全的List实现,也是ArrayList 的安全版本,所以就不会有ArrayList并发修改异常 的问题产生了

原理

每次在对 List 进行修改时,创建一个新的副本(即拷贝),而不是直接在原始列表上进行修改。

在创建新的副本时,CopyOnWriteArrayList复制整个内部数组,并在副本上进行修改操作。所以操作是不会被阻塞的,因为读取操作可以同时原始列表新创建的副本上进行。

由于每次修改都会创建一个新的副本,所以CopyOnWriteArrayList适用于读取操作频繁、写入操作较少的场景。它是可以避免读与写之间的竞争条件,从而提供了较好的并发性能。

虽然CopyOnWriteArrayList提供了线程安全的读取操作,但写入操作的性能相对较低,因为每次写入操作都需要复制整个内部数组。因此,如果需要频繁进行写入操作或对 List 的实时性要求较高,可能不适合使用CopyOnWriteArrayList。

总的来说就是:CopyOnWriteArrayList的原理是通过创建副本来实现线程安全的读取操作,避免了读写竞争条件,但写入操作的性能相对较低。

弊端

1、 每次都要赋值,浪费很大的内存空间

2、读线程可能读不到正在写入的数据,是无法保持 数据的实时一致,只能保证数据的最终一致性。

源码解释

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
   
// 此处是和ArrayList一样的
}

使用的锁

final transient ReentrantLock lock = new ReentrantLock();

装载数组

private transient volatile Object[] array;

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

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

构造器

// 无参构造器
public CopyOnWriteArrayList() {
   
    setArray(new Object[0]);
}

// 有参构造器
public CopyOnWriteArrayList(Collection<? extends E> c) {
   
  Object[] elements;
  if (c.getClass() == CopyOnWriteArrayList.class)
    elements = ((CopyOnWriteArrayList<?>)c).getArray();
  else {
   
    elements = c.toArray();
    if (c.getClass() != ArrayList.class)
      elements = Arrays.copyOf(elements, elements.length, Object[].class);
  }
  setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
   
  setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

get
get(int index)

返回此列表中指定位置的元素。

//返回此列表中指定位置的元素。
public E get(int index) {
   
  return get(getArray(), index);
}
//获取数组
final Object[] getArray() {
   
  return array;
}
get(Object[] a, int index)

返回此列表中指定位置的元素。

private transient volatile Object[] array;

private E get(Object[] a, int index) {
   
    return (E) a[index];
}
contains
contains(Object o)

如果此列表包含指定元素,则返回true 。更正式地说,当且仅当此列表包含至少一个元素e且满足(o==null ? e==null : o.equals(e))时,才返回true

在这里插入图片描述

public boolean contains(Object o) {
   
    Object[] elements = getArray();
   // 遍历集合找到返回true 否则返回false
    return indexOf(o, elements, 0, elements.length) >= 0;
}
containsAll(Collection<?> c)

如果此列表包含指定集合的所有元素,则返回true

在这里插入图片描述

public boolean containsAll(Collection<?> c) {
   
    Object[] elements = getArray();
    int len = elements.length;
  // 遍历指定集合,在挨个遍历原数组中的元素进行匹配,找不到就返回不存在,false
    for (Object e : c) {
   
        if (indexOf(e, elements, 0, len) < 0)
            return false;
    }
  // 否则返回true
    return true;
}

set
set(int index, E element)

将此列表中指定位置的元素替换为指定元素。

在这里插入图片描述

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;
          // 对原来的数组进行copy,并设置新数组的容量 就是 原数组的容量 
            Object[] newElements = Arrays.copyOf(elements, len);
          // 修改新数组指定位置的元素
            newElements[index] = element;
          // 新数组 变为 下次getArray 的数组
            setArray(newElements);
        } else {
   
            // 修改的值和原来的一样,不用修改
            setArray(elements);
        }
      // 返回旧的值
        return oldValue;
    } finally {
   
        lock.unlock();
    }
}
add
add(E e)

此方法会添加原数组就有的重复的元素

将指定元素追加到此列表的末尾。

在这里插入图片描述

public boolean add(E e) {
   
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
          // 获取当前数组
            Object[] elements = getArray();
          // 当前数组的长度
            int len = elements.length;
          // copy 原数组的值到新数组中,并设置新数组的容量是 原数组的容量 + 1 
            Object[] newElements = Arrays.copyOf(elements, len + 1);
          // 在新数组的最后位置添加这个元素
            newElements[len] = e;
          // 新数组 变为 下次getArray 的数组
            setArray(newElements);
            return true;
        } finally {
   
            lock.unlock();
        }
    }

add(int index, E element)

此方法会添加原数组就有的重复的元素

在此列表中的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)

在这里插入图片描述

public void add(int index, E element) {
   
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
        Object[] elements = getArray();
        int len = elements.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
      // 需要移动的元素的个数
        int numMoved = len - index;
      // 在末尾添加
        if (numMoved == 0)
          //copy 原数组的值到新数组中,并设置新数组的容量是 原数组的容量 + 1 
            newElements = Arrays.copyOf(elements, len + 1);
      // 
        else {
   
          //设置新数组的容量是 原数组的容量 + 1 
            newElements = new Object[len + 1];
          // copy 原数组中 指定位置前 和指定位置 后的值到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
       // 指定位置填充元素
        newElements[index] = element;
       // 新数组 变为 下次getArray 的数组
        setArray(newElements);
    } finally {
   
        lock.unlock();
    }
}
addAll(Collection<? extends E> c)

此方法会添加原数组就有的重复的元素

将指定集合中的所有元素按照指定集合的迭代器返回的顺序附加到此列表的末尾。

在这里插入图片描述

public boolean addAll(Collection<? extends E> c) {
   
  // 转为数组
    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
    if (cs.length == 0)
        return false;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
        Object[] elements = getArray();
        int len = elements.length;
       // 要是原数组长度为 0 并且 添加的集合 是  CopyOnWriteArrayList 或者  ArrayList
        if (len == 0 && (c.getClass() == CopyOnWriteArrayList.class || c.getClass() == ArrayList.class)) {
   
           // 新增的集合就指定为 下次getArray 的数组 
           setArray(cs);
        } else {
   
           // copy 原数组的值到新数组中,并设置新数组的容量是 原数组的容量 + 新增集合的长度
            Object[] newElements = Arrays.copyOf(elements, len + cs.length);
           // 将集合c 元素 copy 到末尾
            System.arraycopy(cs, 0, newElements, len, cs.length);
           // 新数组 变为 下次getArray 的数组
            setArray(newElements);
        }
        return true;
    } finally {
   
        lock.unlock();
    }
}
addAll(int index, Collection<? extends E> c)

此方法会添加原数组就有的重复的元素

将指定集合中的所有元素插入到此列表中,从指定位置开始。将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加它们的索引)

在这里插入图片描述

public boolean addAll(int index, Collection<? extends E> c) {
   
    Object[] cs = c.toArray();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
        Object[] elements = getArray();
        int len = elements.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        if (cs.length == 0)
            return false;
        int numMoved = len - index;
        Object[] newElements;
       // 不移动,直接copy 到原来数组的末尾
        if (numMoved == 0)
            newElements = Arrays.copyOf(elements, len + cs.length);
        else {
   
          // 指定位置之前和之后的元素拷贝到 新的副本中
            newElements = new Object[len + cs.length];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index,
                             newElements, index + cs.length,
                             numMoved);
        }
       // 在指定位置之后 填充集合元素
        System.arraycopy(cs, 0, newElements, index, cs.length);
       // // 新数组 变为 下次getArray 的数组
        setArray(newElements);
        return true;
    } finally {
   
        lock.unlock();
    }
}
addAllAbsent(Collection<? extends E> c)

此方法不会添加原数组就有的重复的元素

将指定集合中尚未包含在此列表中的所有元素按照指定集合的迭代器返回的顺序追加到此列表的末尾。

在这里插入图片描述

public int addAllAbsent(Collection<? extends E> c) {
   
    Object[] cs = c.toArray();
  // 创建一个cs的副本,以防止对原始对象的修改影响到cs变量。
    if (c.getClass() != ArrayList.class) {
   
        cs = cs.clone();
    }
    if (cs.length == 0)
        return 0;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
        Object[] elements = getArray();
        int len = elements.length;
        int added = 0;  // 不重复元素的个数
        // uniquify and compact elements in cs
        for (int i = 0; i < cs.length; ++i) {
   
            Object e = cs[i];
          			// 原数组中 和 转为数组的集合中都没有这个元素							
            if (indexOf(e, elements, 0, len) < 0 &&indexOf(e, cs, 0, added) < 0)
                //将不重复的元素添加到 cs 数组中。
                cs[added++] = e;
        }
        if (added > 0) {
   
            Object[] newElements = Arrays.copyOf(elements, len + added);
          	//不重复元素放到新元素的末尾
            System.arraycopy(cs, 0, newElements, len, added);
            setArray(newElements);
        }
       // 返回原数组中不存在的集合中的元素的个数
        return added;
    } finally {
   
        lock.unlock();
    }
}
// 返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1。
// 更正式地说,返回满足(o==null ? get(i)==null : o.equals(get(i))) 的最低索引i(第一个位置) ,如果没有这样的索引,则返回 -1 。

// 遍历数组找到符合的元素,找不到返回 -1
 private static int indexOf(Object o, Object[] elements,int index, int fence) {
   
        if (o == null) {
   
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
   
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

addIfAbsent(E e)

此方法不会添加原数组就有的重复的元素

追加该元素(如果不存在)。

在这里插入图片描述

public boolean addIfAbsent(E e) {
   
    Object[] snapshot = getArray();
  // 要是元素在原来的数组中是存在的直接返回,否则就调用 addIfAbsent(e, snapshot)
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}


private boolean addIfAbsent(E e, Object[] snapshot) {
   
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
            Object[] current = getArray();
            int len = current.length;
          	// 要是数组发生了变化,表示有其他线程正在竞争修改这个数组
            if (snapshot != current) {
   
                // 得到两个数组的公共长度,以小的值为准
                int common = Math.min(snapshot.length, len);
              	// 遍历两个数组的公共部分
                for (int i = 0; i < common; i++)
                   // 两个数组又不想等的元素  && 当前数组已经存在要添加的元素
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                      // 不添加
                        return false;
              // 遍历结束,前面的部分都是一样的
              // 查找当前数组剩余部分的元素 要是此时能找到添加的元素也 不添加
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
          //否则,创建当前数组的副本,并在结尾加上这个元素
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
   
            lock.unlock();
        }
    }
remove
remove(int index)

删除此列表中指定位置的元素。将所有后续元素向左移动(从索引中减去 1)。返回从列表中删除的元素。

在这里插入图片描述

  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;
          // 不要移动  直接拷贝,长度为原来数组的长度 -1
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
   
              // 新建副本,副本的长度减少为原来数组的长度 -1
              // 拷贝到新数组中该位置之前 和 该元素之后 的元素
              // 新数组 变为 下次getArray 的数组
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,numMoved);
                setArray(newElements);
            }
          // 返回旧值
            return oldValue;
        } finally {
   
            lock.unlock();
        }
    }
remove(Object o)

在这里插入图片描述

从此列表中删除第一次出现的指定元素(如果存在)。如果此列表不包含该元素,则它不会更改。更正式地说,删除具有最低索引i元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果存在这样的元素)。如果此列表包含指定的元素(或者等效地,如果此列表由于调用的结果而更改),则返回true 。

public boolean remove(Object o) {
   
    Object[] snapshot = getArray();
    // 判断原数组是否存在当前 元素,并返回当前元素的位置
    int index = indexOf(o, snapshot, 0, snapshot.length);
    // 存在才调用 remove(o, snapshot, index); 移除
    return (index < 0) ? false : remove(o, snapshot, index);
}
remove(Object o, Object[] snapshot, int index)

给定的最近快照在给定索引处包含 o。

在这里插入图片描述

 private boolean remove(Object o, Object[] snapshot, int index) {
   
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
            Object[] current = getArray();
            int len = current.length;
            //  数组发生了变化 
            if (snapshot != current) findIndex: {
   
              // 取删除元素的下标和当前数组长度的最小值 进行 后续遍历
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
   
                  // 数组中的元素出现了变化  并且  要删除的值就是当前位置的值
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
   
                      // 更新要删除的元素的下标
                        index = i;
                      // 结束循环
                        break findIndex;
                    }
                }
              // 要是现在的元素的下标已经超过要当前的数组的长度
                if (index >= len)
                  // 删除失败
                    return false;
              //上面两个数组公共位置的元素都是一样的,那么现在就是当前index位置的元素判断是不是一样的 
                if (current[index] == o)
                    break findIndex; // 是就结束循环
              // 从index位置遍历现在数组的元素,直到结束
                index = indexOf(o, current, index, len);
              // 要是找不到,删除失败
                if (index < 0)
                    return false;
            }
           // 新建副本,副本的长度减少为原来数组的长度 -1
           // 拷贝到新数组中该位置之前 和 该元素之后 的元素
           // 新数组 变为 下次getArray 的数组
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            setArray(newElements);
            return true;
        } finally {
   
            lock.unlock();
        }
    }
removeRange(int fromIndex, int toIndex)

从此列表中删除索引介于fromIndex (包含)和toIndex (不包含)之间的所有元素。将所有后续元素向左移动(减少它们的索引)。此调用将列表缩短(toIndex - fromIndex)个元素。 (如果toIndex==fromIndex ,则此操作无效。)

在这里插入图片描述

 void removeRange(int fromIndex, int toIndex) {
   
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
            Object[] elements = getArray();
            int len = elements.length;

            if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
                throw new IndexOutOfBoundsException();
           // 删除指定范围之后的数组长度
            int newlen = len - (toIndex - fromIndex);
           // 需要移动的元素 
           int numMoved = len - toIndex;
           // 不用移动,直接拷贝之前的元素
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, newlen));
            else {
   
               // 新建副本,副本的长度减少为原来数组的长度 -1
              // 拷贝到新数组中该位置之前 和 该元素之后 的元素
              // 新数组 变为 下次getArray 的数组
                Object[] newElements = new Object[newlen];
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                setArray(newElements);
            }
        } finally {
   
            lock.unlock();
        }
    }
removeAll(Collection<?> c)

从此列表中删除指定集合中包含的所有元素。由于需要内部临时数组,因此在此类中这是一个特别昂贵的操作。

在这里插入图片描述

   public boolean removeAll(Collection<?> c) {
   
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
   
                // temp 数组保存需要保留的元素
                int newlen = 0;
                Object[] temp = new Object[len];
                // 遍历 原数组
                for (int i = 0; i < len; ++i) {
   
                    Object element = elements[i];
                  // 要是删除元素的集合不包含这个元素,塞到temp数组中
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
              // 有删除的元素
                if (newlen != len) {
   
                  // 将temp copy 作为新数组中
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
   
            lock.unlock();
        }
    }
removeIf(Predicate<? super E> filter)

根据指定条件过滤集合元素的功能

在这里插入图片描述

public boolean removeIf(Predicate<? super E> filter) {
    //Predicate<? super E> 类型的参数 filter,用于过滤集合中的元素
    if (filter == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
        Object[] elements = getArray();
        int len = elements.length;
        if (len != 0) {
   
            int newlen = 0;
            Object[] temp = new Object[len];
          //遍历现在的数组,如果元素不满足 filter 的条件,则将其保存在 temp 数组中,并增加 newlen 的值。
            for (int i = 0; i < len; ++i) {
   
                @SuppressWarnings("unchecked") E e = (E) elements[i];
                if (!filter.test(e))
                    temp[newlen++] = e;
            }
          //有元素被删除
            if (newlen != len) {
   
              // 将temp copy 作为新数组中
                setArray(Arrays.copyOf(temp, newlen));
                return true;
            }
        }
        return false;
    } finally {
   
        lock.unlock();
    }
}
clear

从此列表中删除所有元素。该调用返回后列表将为空。

public void clear() {
   
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
   
      // 设置一个空的数组作为新的数组
        setArray(new Object[0]);
    } finally {
   
        lock.unlock();
    }
}

相关推荐

  1. CopyOnWriteArraySet分析

    2023-12-08 08:44:02       30 阅读
  2. springAOP分析

    2023-12-08 08:44:02       43 阅读
  3. 并发编程ReentrantLock分析

    2023-12-08 08:44:02       34 阅读
  4. bert分析tokenization

    2023-12-08 08:44:02       17 阅读
  5. TinyEMU分析译码执行

    2023-12-08 08:44:02       16 阅读
  6. spring分析上下文构建

    2023-12-08 08:44:02       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-08 08:44:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-08 08:44:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-08 08:44:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-08 08:44:02       18 阅读

热门阅读

  1. vue3在table里使用elementUI的form表单验证

    2023-12-08 08:44:02       40 阅读
  2. Boost:asio单io_service,多线程run

    2023-12-08 08:44:02       28 阅读
  3. C++设计模式学习之一(共计13种)

    2023-12-08 08:44:02       32 阅读
  4. LISP~~~~~

    2023-12-08 08:44:02       36 阅读
  5. Spring Kafka常用配置详解

    2023-12-08 08:44:02       33 阅读
  6. c++通过serial库进行上下位机通信

    2023-12-08 08:44:02       39 阅读