如何详细解析CopyOnWriteArrayList的线程安全保障机制?

2026-05-24 03:033阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1530个文字,预计阅读时间需要7分钟。

如何详细解析CopyOnWriteArrayList的线程安全保障机制?

目录:

一、前言

二、成员变量分析

三、源码分析

1. 空参构造 2. 传入一个Collection对象的构造方法 3. 传入一个数组的构造方法

四、总结

一、前言

在确保线程安全的情况下,我们需要考虑如何正确地使用和实现相关的方法。

二、成员变量分析(此处省略具体内容)

三、源码分析

1.空参构造

2.传入一个Collection对象的构造方法

3.传入一个数组的构造方法

(此处省略具体内容)

四、总结(此处省略具体内容)

目录
  • 一:前言
  • 二:成员变量分析
  • 三:源码分析
    • 1.空参构造
    • 2.传入一个Collection对象的构造方法
    • 3.传入一个数组的构造方法
  • 四:总结

    一:前言

    在我们需要保证线程安全的时候,如果使用到Map,那么我们可以使用线程安全的ConcurrentHashMapConcurrentHashMap不仅可以保证线程安全,而且效率也非常不错,那有没有线程安全的List呢?

    如何详细解析CopyOnWriteArrayList的线程安全保障机制?

    答案是有,那就是CopyOnWriteArrayList。今天我们就一起来了解一下CopyOnWriteArrayList,看它是如何巧妙的保证线程安全的吧。

    二:成员变量分析

    //进行修改操作时的锁 final transient ReentrantLock lock = new ReentrantLock(); //真正保存数据的数组 用volatile关键字进行修饰,保证array的引用的可见性 private transient volatile Object[] array;

    三:源码分析

    首先我们看构造方法,CopyOnWriteArrayList有三个构造方法。

    1.空参构造

    调用setArray方法将成员变量array赋值为一个长度为0的数组。

    public CopyOnWriteArrayList() { setArray(new Object[0]); }

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

    2.传入一个Collection对象的构造方法

    首先判断Collection是否是一个CopyOnWriteArrayList,如果是,直接将传入的CopyOnWriteArrayList的elements重新赋值给需要创建的CopyOnWriteArrayList。

    如果不是,判断Collection是否是ArrayList,如果是,那么就利用toArray()方法将其转化为一个数组并赋值给成员变量array,否则将Collection里面的元素全部取出来copy到一个新数组中,并且将该数组赋值给成员变量array。

    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); }

    3.传入一个数组的构造方法

    将传入的数组的元素copy到一个新的Object数组,并且赋值给成员变量array。

    public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }

    接下来我们看核心的add(),remove(),get()方法。

    • add(E e)

    首先加锁,然后通过Arrays.copyOf()方法将元素copy到一个新的数组中,新的数组的长度为原数组的长度+1,并且将需要加入的元素赋值到新数组的最后。最后将新数组赋值给成员变量array。

    public boolean add(E e) { 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; setArray(newElements); return true; } finally { lock.unlock(); } }

    • add(int index, E element)

    add(int index, E element)方法需要将元素加入到指定的索引位置中。首先也是先加锁,保证线程安全,将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy完成之后在将需要插入的元素设置到索引为index的位置上。之后将新数组赋值给成员变量array。

    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) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }

    接下来看remove()方法。

    • remove(int index)

    remove(int index)方法需要在数组中移除指定索引的值。首先是加锁,同样也是将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy到一个新数组中,新数组的长度为原数组的长度减一(注意这里是没有copy index索引位置的值的,所以相当于移除了index索引上的值)。之后将新数组赋值给成员变量array。

    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; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { 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(); } }

    接下来看get()方法

    • get()

    我们可以看到get()方法很简单,就是从array成员变量中取出对应索引的值。并没有加锁处理。所以尽管是在并发高的情况下,get()方法的效率依旧是比较高的。

    /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }

    四:总结

    CopyOnWriteArrayList为什么能够保证线程安全,主要是因为以下几点:

    1.在做修改操作的时候加锁

    2.每次修改都是将元素copy到一个新的数组中,并且将数组赋值到成员变量array中。

    3.利用volatile关键字修饰成员变量array,这样就可以保证array的引用的可见性,每次修改之前都能够拿到最新的array引用。这点很关键。

    看到这里,相信你已经对CopyOnWriteArrayList非常了解了,CopyOnWriteArrayList在查询多,修改操作少的情况下效率是非常可观的,既能够保证线程安全,又能有不错的效率。但是如果修改操作较多,就会导致数组频繁的copy,效率就会有所下降,如果修改操作很多,那么直接使用Collections.synchronizedList(),或许也是一个不错的选择。

    以上就是详解CopyOnWriteArrayList是如何保证线程安全的详细内容,更多关于CopyOnWriteArrayList 线程安全的资料请关注自由互联其它相关文章!

    本文共计1530个文字,预计阅读时间需要7分钟。

    如何详细解析CopyOnWriteArrayList的线程安全保障机制?

    目录:

    一、前言

    二、成员变量分析

    三、源码分析

    1. 空参构造 2. 传入一个Collection对象的构造方法 3. 传入一个数组的构造方法

    四、总结

    一、前言

    在确保线程安全的情况下,我们需要考虑如何正确地使用和实现相关的方法。

    二、成员变量分析(此处省略具体内容)

    三、源码分析

    1.空参构造

    2.传入一个Collection对象的构造方法

    3.传入一个数组的构造方法

    (此处省略具体内容)

    四、总结(此处省略具体内容)

    目录
    • 一:前言
    • 二:成员变量分析
    • 三:源码分析
      • 1.空参构造
      • 2.传入一个Collection对象的构造方法
      • 3.传入一个数组的构造方法
    • 四:总结

      一:前言

      在我们需要保证线程安全的时候,如果使用到Map,那么我们可以使用线程安全的ConcurrentHashMapConcurrentHashMap不仅可以保证线程安全,而且效率也非常不错,那有没有线程安全的List呢?

      如何详细解析CopyOnWriteArrayList的线程安全保障机制?

      答案是有,那就是CopyOnWriteArrayList。今天我们就一起来了解一下CopyOnWriteArrayList,看它是如何巧妙的保证线程安全的吧。

      二:成员变量分析

      //进行修改操作时的锁 final transient ReentrantLock lock = new ReentrantLock(); //真正保存数据的数组 用volatile关键字进行修饰,保证array的引用的可见性 private transient volatile Object[] array;

      三:源码分析

      首先我们看构造方法,CopyOnWriteArrayList有三个构造方法。

      1.空参构造

      调用setArray方法将成员变量array赋值为一个长度为0的数组。

      public CopyOnWriteArrayList() { setArray(new Object[0]); }

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

      2.传入一个Collection对象的构造方法

      首先判断Collection是否是一个CopyOnWriteArrayList,如果是,直接将传入的CopyOnWriteArrayList的elements重新赋值给需要创建的CopyOnWriteArrayList。

      如果不是,判断Collection是否是ArrayList,如果是,那么就利用toArray()方法将其转化为一个数组并赋值给成员变量array,否则将Collection里面的元素全部取出来copy到一个新数组中,并且将该数组赋值给成员变量array。

      public CopyOnWriteArrayList(Collection&lt;? extends E&gt; c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList&lt;?&gt;)c).getArray(); else { elements = c.toArray(); if (c.getClass() != ArrayList.class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }

      3.传入一个数组的构造方法

      将传入的数组的元素copy到一个新的Object数组,并且赋值给成员变量array。

      public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }

      接下来我们看核心的add(),remove(),get()方法。

      • add(E e)

      首先加锁,然后通过Arrays.copyOf()方法将元素copy到一个新的数组中,新的数组的长度为原数组的长度+1,并且将需要加入的元素赋值到新数组的最后。最后将新数组赋值给成员变量array。

      public boolean add(E e) { 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; setArray(newElements); return true; } finally { lock.unlock(); } }

      • add(int index, E element)

      add(int index, E element)方法需要将元素加入到指定的索引位置中。首先也是先加锁,保证线程安全,将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy完成之后在将需要插入的元素设置到索引为index的位置上。之后将新数组赋值给成员变量array。

      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) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }

      接下来看remove()方法。

      • remove(int index)

      remove(int index)方法需要在数组中移除指定索引的值。首先是加锁,同样也是将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy到一个新数组中,新数组的长度为原数组的长度减一(注意这里是没有copy index索引位置的值的,所以相当于移除了index索引上的值)。之后将新数组赋值给成员变量array。

      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; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { 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(); } }

      接下来看get()方法

      • get()

      我们可以看到get()方法很简单,就是从array成员变量中取出对应索引的值。并没有加锁处理。所以尽管是在并发高的情况下,get()方法的效率依旧是比较高的。

      /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }

      四:总结

      CopyOnWriteArrayList为什么能够保证线程安全,主要是因为以下几点:

      1.在做修改操作的时候加锁

      2.每次修改都是将元素copy到一个新的数组中,并且将数组赋值到成员变量array中。

      3.利用volatile关键字修饰成员变量array,这样就可以保证array的引用的可见性,每次修改之前都能够拿到最新的array引用。这点很关键。

      看到这里,相信你已经对CopyOnWriteArrayList非常了解了,CopyOnWriteArrayList在查询多,修改操作少的情况下效率是非常可观的,既能够保证线程安全,又能有不错的效率。但是如果修改操作较多,就会导致数组频繁的copy,效率就会有所下降,如果修改操作很多,那么直接使用Collections.synchronizedList(),或许也是一个不错的选择。

      以上就是详解CopyOnWriteArrayList是如何保证线程安全的详细内容,更多关于CopyOnWriteArrayList 线程安全的资料请关注自由互联其它相关文章!