• CopyOnWriteArrayList源码分析


    CopyOnWriteArrayList是Copy-On-Write简称COW的一种实现。

    CopyOnWriteArrayList是线程安全的,内部结构为Object[]数组,允许多个线程并发读取,但是只可以有一个县城写入。

    特性:

    1. 在保证并发读取的前提下,确保了写入时的线程安全;
    2. 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;
    3. 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
    4. CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。

     准备工作

    1. implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    2. //定义了一个可重入锁
    3. final transient ReentrantLock lock = new ReentrantLock();
    4. //一个私有的且安全的、可见的数组
    5. private transient volatile Object[] array;
    6. //得到数组的方法
    7. final Object[] getArray() {
    8. return array;
    9. }
    10. //得到数组的值
    11. final void setArray(Object[] a) {
    12. array = a;
    13. }

    无参构造方法

    1. //复制数组的方法
    2. public CopyOnWriteArrayList() {
    3. setArray(new Object[0]);
    4. }

    有参构造方法 

    1. public CopyOnWriteArrayList(Collection<? extends E> c) {
    2. //原始数组
    3. Object[] elements;
    4. //判断传入的数组的方法和复制数组的方法是否相同
    5. if (c.getClass() == CopyOnWriteArrayList.class)
    6. //相同则直接将传入的数组存入原始数组中
    7. elements = ((CopyOnWriteArrayList<?>)c).getArray();
    8. else {
    9. //直接将当前集合c赋值给elements
    10. elements = c.toArray();
    11. // c.toArray might (incorrectly) not return Object[] (see 6260652)
    12. //数组类型不是Object[]类型,对数组进行复制操作
    13. if (elements.getClass() != Object[].class)
    14. elements = Arrays.copyOf(elements, elements.length, Object[].class);
    15. }
    16. setArray(elements);
    17. }

    修改指定下标的值 

    1. public E set(int index, E element) {
    2. final ReentrantLock lock = this.lock;
    3. lock.lock();
    4. try {
    5. Object[] elements = getArray();
    6. //得到指定下标的值
    7. E oldValue = get(elements, index);
    8. //判断这个得到的值和将要存入的值是否相等
    9. if (oldValue != element) {
    10. //计算原始数组的长度
    11. int len = elements.length;
    12. //将原始数组复制到新数组中
    13. Object[] newElements = Arrays.copyOf(elements, len);
    14. //在新数组的指定下标存入 将要存进去的值
    15. newElements[index] = element;
    16. //原始数组将引用指向新数组
    17. setArray(newElements);
    18. } else {
    19. // Not quite a no-op; ensures volatile write semantics
    20. setArray(elements);
    21. }
    22. return oldValue;
    23. } finally {
    24. lock.unlock();
    25. }
    26. }

    添加元素至尾部 

    1. public boolean add(E e) {
    2. //调用ReentrantLock锁
    3. final ReentrantLock lock = this.lock;
    4. //加锁
    5. lock.lock();
    6. try {
    7. //得到原始数组的值
    8. Object[] elements = getArray();
    9. //计算原始数组的长度
    10. int len = elements.length;
    11. //通过copyOf方法将原始数组复制到新数组中并且把新数组的长度加一
    12. Object[] newElements = Arrays.copyOf(elements, len + 1);
    13. //把新元素添加到新数组中
    14. newElements[len] = e;
    15. //将原始数组引向新数组
    16. setArray(newElements);
    17. return true;
    18. } finally {
    19. //解锁
    20. lock.unlock();
    21. }
    22. }

    在指定下标位置添加元素 

    1. public void add(int index, E element) {
    2. final ReentrantLock lock = this.lock;
    3. lock.lock();
    4. try {
    5. //得到原始数组的值
    6. Object[] elements = getArray();
    7. //计算原始数组的长度
    8. int len = elements.length;
    9. //判断指定下标是否在数组长度范围内
    10. if (index > len || index < 0)
    11. //抛出数组下标越界异常
    12. throw new IndexOutOfBoundsException("Index: "+index+
    13. ", Size: "+len);
    14. //定义新数组
    15. Object[] newElements;
    16. //计算数组长度与指定下标的差
    17. int numMoved = len - index - 1;
    18. //如果等于0,则在数组的最后一位添加值
    19. if (numMoved == 0)
    20. //通过copyOf方法将原始数组复制到新数组中并且把新数组的长度加一
    21. newElements = Arrays.copyOf(elements, len + 1);
    22. else {
    23. //重新定义新数组的长度加一
    24. newElements = new Object[len + 1];
    25. //将原始数组直接复制进入新数组
    26. System.arraycopy(elements, 0, newElements, 0, index);
    27. //将原始数组的值从0到指定的index复制到新数组的index+1的长度
    28. System.arraycopy(elements, index, newElements, index + 1,
    29. numMoved);
    30. }
    31. //把新元素添加到新数组中
    32. newElements[index] = element;
    33. //将原始数组引向新数组
    34. setArray(newElements);
    35. } finally {
    36. //释放锁
    37. lock.unlock();
    38. }
    39. }

    删除指定下标的元素 

    1. public E remove(int index) {
    2. final ReentrantLock lock = this.lock;
    3. lock.lock();
    4. try {
    5. //得到原始数组的值
    6. Object[] elements = getArray();
    7. //计算原始数组的长度
    8. int len = elements.length;
    9. //得到原来数组中的指定下标的值
    10. E oldValue = get(elements, index);
    11. //计算原始数组的长度与指定下标的差
    12. int numMoved = len - index - 1;
    13. //0
    14. if (numMoved == 0)
    15. //则删除的是原始数组的最后一位,则只需将原始数组提前一位复制并且得到值
    16. setArray(Arrays.copyOf(elements, len - 1));
    17. else {
    18. //定义一个新的数组并且长度减一符合删除一位后的长度
    19. Object[] newElements = new Object[len - 1];
    20. //将原始数组复制到新数组中并且复制指定下标大小的长度
    21. System.arraycopy(elements, 0, newElements, 0, index);
    22. //将原始数组从指定下标加一开始复制至新数组中从指定下标开始存入,复制剩余长度个
    23. System.arraycopy(elements, index + 1, newElements, index,
    24. numMoved);
    25. //将原始数组的引用指向新数组
    26. setArray(newElements);
    27. }
    28. //返回老数组的值
    29. return oldValue;
    30. } finally {
    31. //释放锁
    32. lock.unlock();
    33. }
    34. }

    删除指定范围的元素 

    1. //从开始下标删除至末尾下标
    2. void removeRange(int fromIndex, int toIndex) {
    3. final ReentrantLock lock = this.lock;
    4. //加锁
    5. lock.lock();
    6. try {
    7. Object[] elements = getArray();
    8. int len = elements.length;
    9. //判断开始下标和末尾下标是否符合条件
    10. if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
    11. //并且抛出下标越界异常
    12. throw new IndexOutOfBoundsException();
    13. //得到除了要复制的长度之外剩余的数组的长度
    14. int newlen = len - (toIndex - fromIndex);
    15. //计算出复制的第一段的长度的剩余长度
    16. int numMoved = len - toIndex - 1;
    17. //等于0则删除的是最后一位
    18. if (numMoved == 0)
    19. //直接得到对原始数组的复制但是只复制newlen长度
    20. setArray(Arrays.copyOf(elements, newlen));
    21. else {
    22. //定义新数组并且长度为原始数组删除指定段落之外的长度
    23. Object[] newElements = new Object[newlen];
    24. //复制原始数组的内容至新数组中并且复制开始指定下标的长度
    25. System.arraycopy(elements, 0, newElements, 0, fromIndex);
    26. //将原始数组的toIndex长度的值复制到新数组的fromIndex的位置开始并且复制剩余的长度
    27. System.arraycopy(elements, toIndex, newElements,
    28. fromIndex, numMoved);
    29. //将原始数组的引用指向新数组
    30. setArray(newElements);
    31. }
    32. } finally {
    33. //释放锁
    34. lock.unlock();
    35. }
    36. }

    复制保持原来的整个数组 

    1. public boolean retainAll(Collection<?> c) {
    2. //判断当前这个集合是否为null 并且抛出异常类型
    3. if (c == null) throw new NullPointerException();
    4. final ReentrantLock lock = this.lock;
    5. lock.lock();
    6. try {
    7. //得到原始数组的值
    8. Object[] elements = getArray();
    9. //计算原始数组的长度
    10. int len = elements.length;
    11. //判断长度是否为0
    12. if (len != 0) {
    13. // temp array holds those elements we know we want to keep
    14. //给新的长度赋值为0
    15. int newlen = 0;
    16. //定义一个新数组
    17. Object[] temp = new Object[len];
    18. //进入循环
    19. for (int i = 0; i < len; ++i) {
    20. //取出原始数组的值
    21. Object element = elements[i];
    22. //判断在集合中是否存在
    23. if (c.contains(element))
    24. //存在则集合长度加一 并且将值存入
    25. temp[newlen++] = element;
    26. }
    27. //如果长度不等于0
    28. if (newlen != len) {
    29. //直接复制数组
    30. setArray(Arrays.copyOf(temp, newlen));
    31. return true;
    32. }
    33. }
    34. return false;
    35. } finally {
    36. lock.unlock();
    37. }
    38. }

    清空集合 

    1. public void clear() {
    2. final ReentrantLock lock = this.lock;
    3. lock.lock();
    4. try {
    5. //将原数组的引用指向新数组并且长度为0 则清空
    6. setArray(new Object[0]);
    7. } finally {
    8. lock.unlock();
    9. }
    10. }

    在指定位置批量添加元素 

    1. public boolean addAll(int index, Collection<? extends E> c) {
    2. //将集合存入数组中
    3. Object[] cs = c.toArray();
    4. final ReentrantLock lock = this.lock;
    5. lock.lock();
    6. try {
    7. //得到原始数组
    8. Object[] elements = getArray();
    9. //得到原始数组的长度
    10. int len = elements.length;
    11. //判断得到的长度是否符合下标
    12. if (index > len || index < 0)
    13. //抛出下标越界异常类型
    14. throw new IndexOutOfBoundsException("Index: "+index+
    15. ", Size: "+len);
    16. //判断这个数组长度是否为0
    17. if (cs.length == 0)
    18. return false;
    19. //计算长度减去下标后剩余的长度
    20. int numMoved = len - index;
    21. //定义新数组
    22. Object[] newElements;
    23. //等于0则添加到末尾
    24. if (numMoved == 0)
    25. //在原始数组的最后一位加入这个新集合
    26. newElements = Arrays.copyOf(elements, len + cs.length);
    27. else {
    28. //定义新数组的长度
    29. newElements = new Object[len + cs.length];
    30. //直接复制数组
    31. System.arraycopy(elements, 0, newElements, 0, index);
    32. //在指定下标将加入的集合复制进去,
    33. System.arraycopy(elements, index,
    34. newElements, index + cs.length,
    35. numMoved);
    36. }
    37. //输出复制数组的值
    38. System.arraycopy(cs, 0, newElements, index, cs.length);
    39. //将原始数组引用指向新数组中
    40. setArray(newElements);
    41. return true;
    42. } finally {
    43. lock.unlock();
    44. }
    45. }

     

  • 相关阅读:
    20.(arcgis api for js篇)arcgis api for js面采集(SketchViewModel)
    聊聊操作系统中 进程 and 线程中哪些事??
    __int128类型movaps指令crash
    第六章《类的高级特性》第1节:static关键字的使用
    java8 新特性 -Optional的常见用法
    windows10复制文件需要管理员权限,复制需要管理员权限怎么办
    SQL(Structured Query Language)—结构化查询语言
    “被裁员“! 有这份面试突击手册! 一周斩获6个offer!
    EVALUATING HALLUCINATIONS IN CHINESE LARGE LANGUAGE MODELS
    关于C++解决内存泄漏问题的心得
  • 原文地址:https://blog.csdn.net/qq_46642326/article/details/126923913