礼华 發表於 2025-10-11 10:40:00

CopyOnWriteArrayList 的故事--一起看看java原生的读写分离

<p><span style="font-family: &quot;Microsoft YaHei&quot;">CopyOnWriteArrayList 是JUC中,为了实现高并发而提供的list容器之一。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">对于大部分的业务场景,都是读多写少,并发度也基本卡在了读的位置。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">通常支持并发的容器在解决并发时,采用是:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(1)数据分割,每个线程只操作属于当前线程自己的数据,如ThreadLocal (感兴趣的同学可以看我前文 《Java内存模型及Java关键字 volatile的作用和使用说明》&nbsp; https://www.cnblogs.com/jilodream/p/9452391.html)</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(2)元数据被操作时,通过锁来进行并发控制。(如Hashtable 、concurrentHashMap 等)</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">今天要说的CopyOnWriteArrayList的思想是<span style="color: rgba(255, 0, 0, 1)"><strong>读写分离</strong></span>:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">如图,他的思路大概是这样的,</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202510/704073-20251011104422750-307323429.png" alt="cowlist" loading="lazy"></p>
<p>&nbsp;</p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">(0)提供一个数组来作为队列:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">(1)所有的读操作直接读取数组,由于只有读操作,因此不会存在数据安全问题。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(2)所有的写操作是会将原始数据(数组)+改动操作(增删数据) 直接体现到另外一个新的数组中。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )在写操作完毕之前,替换为原有的,外部读可以访问的数组。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(3)由于写操作才会有并发的问题,因此所有的写操作是通过锁来隔离的。而读操作都是建立在一个完整的,不被改动的数组中的,因此读也就不再需要锁了。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">先来看下命名</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">Copy复制/拷贝</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">On在...时间点</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">Write 写</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">Array数组</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">List队列</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">结合起来就是一个在写数据时进行拷贝的数组队列。一语道破该并发容器的核心。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">怎么使用CopyOnWriteArrayList就不说了,他的常规使用与其他List并没有太大的区别。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">我们直接来看源码(我这里采用的是JDK17):</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">首先来看定义</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> CopyOnWriteArrayList&lt;E&gt;
    <span style="color: rgba(0, 0, 255, 1)">implements</span> List&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">定义中首先指定该容器依次实现了</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">List&lt;E&gt;, ---&gt; 队列接口</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">RandomAccess, ---&gt; 标记接口,支持随机访问</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">Cloneable, ---&gt; 标记访问,支持拷贝</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">java.io.Serializable ---&gt;标记接口,支持可序列化</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">至于什么是标记接口,可以看我的这篇文章(为什么这些java接口没有抽象方法?浅谈Java标记接口&nbsp; https://www.cnblogs.com/jilodream/p/5986519.html)。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(ps. 不知道大家发现没有代表能力的接口jdk往往喜欢加-able,表示可以的。这在代码简洁之道这本书中也是推荐的命名方式之一。)</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">我们接下来看下它的核心源码。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">首先是元数据部分</span>:</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * The lock protecting all mutators.(We have a mild preference
   * for builtin monitors over ReentrantLock when either will do.)
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">transient</span> Object lock = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Object();

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> The array, accessed only via getArray/setArray. </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">transient</span> <span style="color: rgba(0, 0, 255, 1)">volatile</span> Object[] array;</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">定义了一个Object lock作为全局锁。用final transient 来修饰。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">final 是为了防止锁被乱改,导致出现安全问题。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">transient 则是表示在自动序列化时,不要主动来序列化该字段。这里也是了为了数据安全考虑的,防止正反序列化后,实例中的lock对象暴露出去,导致有安全问题。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">注意在JDK1.8中,锁的实现使用的是ReentrantLock。但是在后续的版本中由于synchronized锁的优化,又使用了syncronized锁。源码中的注释也有这样写:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * The lock protecting all mutators.(We have a mild preference
   * for builtin monitors over ReentrantLock when either will do.)
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="font-size: 13px; font-family: &quot;Microsoft YaHei&quot;"><strong><span style="color: rgba(0, 0, 0, 1)">
   当内置锁和 ReentrantLock 都能用的时候,我们略微倾向于使用内置锁。</span></strong></span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">定义了用来存放Object[] array;使用了transient volatile来修饰。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">使用transient是为了解决序列化的问题。<strong><span style="color: rgba(204, 153, 255, 1)">这里就会有一个疑问,为什么支持序列化,</span></strong>(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )<strong><span style="color: rgba(204, 153, 255, 1)">并且又要在array 前边修饰transient,防止它序列化?这是为何,我们序列化不就是为了转存数据么?</span></strong></span><br><span style="font-family: &quot;Microsoft YaHei&quot;">使用volatile修饰是为了保证array 发生变化时,所有直接使用的线程可以及时的感知到。这里需要对volatile有一定的认知才能明白,不太明白的同学请先了解</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">首先是构造方法:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CopyOnWriteArrayList() {
      setArray(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Object);
    }</span></pre>
</div>
<p><strong><span style="font-family: &quot;Microsoft YaHei&quot;; color: rgba(204, 153, 255, 1)">注意看CopyOnWriteArrayList是将一个len=0的数组初始化到容器中的。而并不像ArrayList一样预留一个初始化大小的数组。这是为什么呢?</span></strong></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">然后是核心的读方法:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> E get(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> elementAt(getArray(), index);
    }
   
      </span><span style="color: rgba(0, 0, 255, 1)">static</span> &lt;E&gt; E elementAt(Object[] a, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (E) a;
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">很简单,从指定的数组中直接读出对应位置的元素,返回</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">核心的四个写方法:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; color: rgba(255, 0, 0, 1)">(1)队列添加全新元素</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> add(E e) {
      </span><span style="color: rgba(0, 0, 255, 1)">synchronized</span><span style="color: rgba(0, 0, 0, 1)"> (lock) {
            Object[] es </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> len =<span style="color: rgba(0, 0, 0, 1)"> es.length;
            es </span>= Arrays.copyOf(es, len + 1<span style="color: rgba(0, 0, 0, 1)">);
            es </span>=<span style="color: rgba(0, 0, 0, 1)"> e;
            setArray(es);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">1、写方法直接加了一把全局锁。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">2、然后在同步的代码块中,将元数据加入到一个(比当前数组长度+1长度的)新数组中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">3、接着将新元素添加到队列的末尾中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">4、最后将新数组覆盖到原数组中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">5、由于原数组在定义时,使用了volatile关键字修饰。因此下次的读操作就会立刻感知到该元素,</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; color: rgba(255, 0, 0, 1)">(2)队列指定位置添加全新元素</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> add(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index, E element) {
      </span><span style="color: rgba(0, 0, 255, 1)">synchronized</span><span style="color: rgba(0, 0, 0, 1)"> (lock) {
            Object[] es </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> len =<span style="color: rgba(0, 0, 0, 1)"> es.length;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (index &gt; len || index &lt; 0<span style="color: rgba(0, 0, 0, 1)">)
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexOutOfBoundsException(outOfBounds(index, len));
            Object[] newElements;
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> numMoved = len -<span style="color: rgba(0, 0, 0, 1)"> index;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (numMoved == 0<span style="color: rgba(0, 0, 0, 1)">)
                newElements </span>= Arrays.copyOf(es, len + 1<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                newElements </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Object;
                System.arraycopy(es, </span>0, newElements, 0<span style="color: rgba(0, 0, 0, 1)">, index);
                System.arraycopy(es, index, newElements, index </span>+ 1<span style="color: rgba(0, 0, 0, 1)">,
                                 numMoved);
            }
            newElements </span>=<span style="color: rgba(0, 0, 0, 1)"> element;
            setArray(newElements);
      }
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">1、写方法直接加了一把全局锁。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">2、然后判断当前添加位置的索引是否合法超出了len的合理范围,如果是,那么就抛出异常。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">3、然后判断当前添加位置是不是数组的最后位置:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">&lt;1&gt;如果是的话,就把原数组的所有元素拷贝到新数组的中。新数组的长度等于老数组+1。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">&lt;2&gt;如果不是的话,就直接new 一个长度为len+1的数组。然后分别把老数组的前后两部分拷贝到新数组中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">4、将添加位置的元素设置为新元素。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; color: rgba(255, 0, 0, 1)">(3)移出指定位置的元素</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> E remove(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index) {
      </span><span style="color: rgba(0, 0, 255, 1)">synchronized</span><span style="color: rgba(0, 0, 0, 1)"> (lock) {
            Object[] es </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> len =<span style="color: rgba(0, 0, 0, 1)"> es.length;
            E oldValue </span>=<span style="color: rgba(0, 0, 0, 1)"> elementAt(es, index);
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> numMoved = len - index - 1<span style="color: rgba(0, 0, 0, 1)">;
            Object[] newElements;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (numMoved == 0<span style="color: rgba(0, 0, 0, 1)">)
                newElements </span>= Arrays.copyOf(es, len - 1<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                newElements </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Object;
                System.arraycopy(es, </span>0, newElements, 0<span style="color: rgba(0, 0, 0, 1)">, index);
                System.arraycopy(es, index </span>+ 1<span style="color: rgba(0, 0, 0, 1)">, newElements, index,
                                 numMoved);
            }
            setArray(newElements);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> oldValue;
      }
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">1、移出方法首先加入一把全局锁</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">2、获取指定位置的元素</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">3、判断移出元素是不是队列的末尾:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">如果是的话,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )就把原数组的去除末尾元素的部分拷贝到新数组的中。新数组的长度等于老数组-1。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">如果不是的话,就直接new 一个长度为len-1的数组。然后分别把老数组的前后两部分拷贝到新数组中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">4、返回查询到的指定元素</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; color: rgba(255, 0, 0, 1)">(4)移出指定元素</span></p>
<div class="cnblogs_code">
<pre>   <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> remove(Object o) {
      Object[] snapshot </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> index = indexOfRange(o, snapshot, 0<span style="color: rgba(0, 0, 0, 1)">, snapshot.length);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> index &gt;= 0 &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> remove(o, snapshot, index);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> remove(Object o, Object[] snapshot, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index) {
      </span><span style="color: rgba(0, 0, 255, 1)">synchronized</span><span style="color: rgba(0, 0, 0, 1)"> (lock) {
            Object[] current </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> len =<span style="color: rgba(0, 0, 0, 1)"> current.length;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (snapshot !=<span style="color: rgba(0, 0, 0, 1)"> current) findIndex: {
                </span><span style="color: rgba(0, 0, 255, 1)">int</span> prefix =<span style="color: rgba(0, 0, 0, 1)"> Math.min(index, len);
                </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; prefix; i++<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (current !=<span style="color: rgba(0, 0, 0, 1)"> snapshot
                        </span>&amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> Objects.equals(o, current)) {
                        index </span>=<span style="color: rgba(0, 0, 0, 1)"> i;
                        </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)"> findIndex;
                  }
                }
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (index &gt;=<span style="color: rgba(0, 0, 0, 1)"> len)
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (current ==<span style="color: rgba(0, 0, 0, 1)"> o)
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)"> findIndex;
                index </span>=<span style="color: rgba(0, 0, 0, 1)"> indexOfRange(o, current, index, len);
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (index &lt; 0<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            Object[] newElements </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Object;
            System.arraycopy(current, </span>0, newElements, 0<span style="color: rgba(0, 0, 0, 1)">, index);
            System.arraycopy(current, index </span>+ 1<span style="color: rgba(0, 0, 0, 1)">,
                           newElements, index,
                           len </span>- index - 1<span style="color: rgba(0, 0, 0, 1)">);
            setArray(newElements);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
    </span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">1、先获取当前元素的位置</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">2、获取元素对应的位置(注意如果有多个相同的元素这里只取第一个找到的元素o),我们记为index</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">3、然后跳转到另外一个remove 重载的方法中。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">4、在重载方法中加入全局锁</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">5、取出最新的数组current</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">6、判断新数组的current和之前查找时用的数组是不是一个数组:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   &lt;1&gt;如果不是,说明有其他线程并发写操作了,此时开始重新找。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   &lt;2&gt;首先在刚才index之前的元素都找一遍,如果找到,说明前移了,或者在index的更前方又插入该元素,此时这个新位置就是要移除的值。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   &lt;3&gt;如果没找到又判断index&gt;=新数组长度,那就说明是没有这个元素o了,直接就返回false了(因为上一步找的是index的位置,如果index都&gt;=新数组长度,那么其实就已经遍历了)。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   &lt;4&gt;如果还没有,那么判断index的位置是否仍等于元素o,如果是那么也判断找到了。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   &lt;5&gt;如果还没有就判断index后边的位置是否有元素等于o</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">   这里其实相当于分了3部分,如果发生了写操作就判断 index之前,index的位置,index之后,这三块分别有没有o出现。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">7、直接new 一个长度为len-1的数组。然后分别把老数组的前后两部分拷贝到新数组中。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">最后这个remove方法其实看的人觉得很繁琐,为什么他没有直接进来一把全局锁,然后遍历查找最后生成新数组即可。其实我觉得是可以的,jdk1.7也的确是这样的。但是在后续的版本中将预查找的部分移出了锁的范围。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">锁的粒度更小。对写的操作更友好。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">以上就是CopyOnWriteArrayList的内部核心实现了。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">现在又回到最初的问题。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )<strong><span style="color: rgba(204, 153, 255, 1)">为什么元数组array的len始终和当前存储的元素数保持一致。而不是像arraylist等其他容器,给一定的预留空间方便,方便写呢?</span></strong></span><br><span style="font-family: &quot;Microsoft YaHei&quot;">我认为主要基于以下考虑:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">CopyOnWriteArrayList本质是为了读写分离更准确的说是为了读多写少的场景设计的。尽可能的压缩数组也为数组拷贝提供了优势。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">array是及时变量,每次使用都是全新new一个出来,并不是在原有基础上写,没有必要为了性能预留空间。(核心原因)</span></p>
<p><strong><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="color: rgba(204, 153, 255, 1)">另外的一个疑问为什么支持序列化</span>,<span style="color: rgba(204, 153, 255, 1)">又要在array 前边修饰transient,防止它序列化?这是为何,我们序列化不就是为了转存数据么?</span></span></strong><br><span style="font-family: &quot;Microsoft YaHei&quot;">这主要是由于CopyOnWriteArrayList是使用写单独拷贝的方式来处理并发的。而array前边又有volatile修饰,也就是说array一旦发生变化,那么所有使用的线程在下次使用时就会立刻感知到。倘若我们序列化进行中,突然发生了写操作,并已经完成,我是应该接着序列化(此时就有错误),还是重新序列化(性能太差)?</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">因此又单独实现了该方法配合序列化用:</span></p>
<div class="cnblogs_code">
<pre>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> writeObject(java.io.ObjectOutputStream s)
      </span><span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> java.io.IOException {

      s.defaultWriteObject();

      Object[] es </span>=<span style="color: rgba(0, 0, 0, 1)"> getArray();
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Write out array length</span>
<span style="color: rgba(0, 0, 0, 1)">      s.writeInt(es.length);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Write out all elements in the proper order.</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Object element : es)
            s.writeObject(element);
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">也就是我单独取出一份当前的数组放到es中,后边不管容器怎么写,我只序列化es中数据。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">除此之外,队列的迭代也会有类似的问题。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">我们知道队列的迭代,本质上就是使用迭代器依次的迭代元数据。而在CopyOnWriteArrayList中,和序列化相似,在迭代时,是将当前的元数组的引用指向到snapshot变量中。后续的迭代操作都是对该snapshot来操作的。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">也就是说,在并发迭代时,我们迭代的数据是当前缓存的历史快照数据。如下:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> Iterator&lt;E&gt;<span style="color: rgba(0, 0, 0, 1)"> iterator() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> COWIterator&lt;E&gt;(getArray(), 0<span style="color: rgba(0, 0, 0, 1)">);
    }
   
    </span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> COWIterator&lt;E&gt; <span style="color: rgba(0, 0, 255, 1)">implements</span> ListIterator&lt;E&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> Snapshot of the array </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> Object[] snapshot;
      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> Index of element to be returned by subsequent call to next.</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> cursor;

      COWIterator(Object[] es, </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> initialCursor) {
            cursor </span>=<span style="color: rgba(0, 0, 0, 1)"> initialCursor;
            snapshot </span>=<span style="color: rgba(0, 0, 0, 1)"> es;
      }
    ....
    }</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">可以看这个例子:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CopyOnWriteArrayListLearn {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
      CopyOnWriteArrayList</span>&lt;Integer&gt; list = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CopyOnWriteArrayList();
      list.add(</span>1<span style="color: rgba(0, 0, 0, 1)">);
      list.add(</span>2<span style="color: rgba(0, 0, 0, 1)">);
      list.add(</span>3<span style="color: rgba(0, 0, 0, 1)">);
      list.add(</span>4<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Integer item : list) {
            list.add(item </span>* 10<span style="color: rgba(0, 0, 0, 1)">);
            System.out.println(</span>"item:" +<span style="color: rgba(0, 0, 0, 1)"> item);
      }
    }
}</span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">输出如下,只输出了迭代开启一瞬间的容器中的元素:</span></p>
<div class="cnblogs_code">
<pre>Connected to the target VM, address: '127.0.0.1:61377', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">
item:</span>1<span style="color: rgba(0, 0, 0, 1)">
item:</span>2<span style="color: rgba(0, 0, 0, 1)">
item:</span>3<span style="color: rgba(0, 0, 0, 1)">
item:</span>4<span style="color: rgba(0, 0, 0, 1)">
Disconnected from the target VM, address: </span>'127.0.0.1:61377', transport: 'socket'</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">同时,直接使用迭代器进行写操作时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )就会因为是快照数据而没有意义,所以源码中直接将迭代器的写操作抛出异常了:</span></p>
<div class="cnblogs_code">
<pre>      <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> remove() {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UnsupportedOperationException();
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Not supported. Always throws UnsupportedOperationException.
         * </span><span style="color: rgba(128, 128, 128, 1)">@throws</span><span style="color: rgba(0, 128, 0, 1)"> UnsupportedOperationException always; {</span><span style="color: rgba(128, 128, 128, 1)">@code</span><span style="color: rgba(0, 128, 0, 1)"> set}
         *         is not supported by this iterator.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> set(E e) {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UnsupportedOperationException();
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Not supported. Always throws UnsupportedOperationException.
         * </span><span style="color: rgba(128, 128, 128, 1)">@throws</span><span style="color: rgba(0, 128, 0, 1)"> UnsupportedOperationException always; {</span><span style="color: rgba(128, 128, 128, 1)">@code</span><span style="color: rgba(0, 128, 0, 1)"> add}
         *         is not supported by this iterator.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> add(E e) {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UnsupportedOperationException();
      }</span></pre>
</div>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <p style="background: #C0FFFF; padding: 10px; border: 1px dashed #E0E0E0; font: 100% 微软雅黑; color: #F00; text-align: center">
如果你觉得写的不错,欢迎转载和点赞。
转载时请保留作者署名jilodream/王若伊_恩赐解脱(博客链接:http://www.cnblogs.com/jilodream/</p><br><br>
来源:https://www.cnblogs.com/jilodream/p/19134525
頁: [1]
查看完整版本: CopyOnWriteArrayList 的故事--一起看看java原生的读写分离