<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      CopyOnWriteArrayList 的故事--一起看看java原生的讀寫分離

      CopyOnWriteArrayList 是JUC中,為了實現(xiàn)高并發(fā)而提供的list容器之一。
      對于大部分的業(yè)務場景,都是讀多寫少,并發(fā)度也基本卡在了讀的位置。
      通常支持并發(fā)的容器在解決并發(fā)時,采用是:
      (1)數(shù)據(jù)分割,每個線程只操作屬于當前線程自己的數(shù)據(jù),如ThreadLocal (感興趣的同學可以看我前文 《Java內(nèi)存模型及Java關鍵字 volatile的作用和使用說明》  http://www.rzrgm.cn/jilodream/p/9452391.html)
      (2)元數(shù)據(jù)被操作時,通過鎖來進行并發(fā)控制。(如Hashtable 、concurrentHashMap 等)
      今天要說的CopyOnWriteArrayList的思想是讀寫分離
      如圖,他的思路大概是這樣的,

      cowlist

       

      (0)提供一個數(shù)組來作為隊列:

      (1)所有的讀操作直接讀取數(shù)組,由于只有讀操作,因此不會存在數(shù)據(jù)安全問題。
      (2)所有的寫操作是會將原始數(shù)據(jù)(數(shù)組)+改動操作(增刪數(shù)據(jù)) 直接體現(xiàn)到另外一個新的數(shù)組中。(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )在寫操作完畢之前,替換為原有的,外部讀可以訪問的數(shù)組。
      (3)由于寫操作才會有并發(fā)的問題,因此所有的寫操作是通過鎖來隔離的。而讀操作都是建立在一個完整的,不被改動的數(shù)組中的,因此讀也就不再需要鎖了。

      先來看下命名
      Copy 復制/拷貝
      On 在...時間點
      Write 寫
      Array 數(shù)組
      List 隊列
      結(jié)合起來就是一個在寫數(shù)據(jù)時進行拷貝的數(shù)組隊列。一語道破該并發(fā)容器的核心。
      怎么使用CopyOnWriteArrayList就不說了,他的常規(guī)使用與其他List并沒有太大的區(qū)別。
      我們直接來看源碼(我這里采用的是JDK17):
      首先來看定義

      public class CopyOnWriteArrayList<E>
          implements List<E>, RandomAccess, Cloneable, java.io.Serializable

      定義中首先指定該容器依次實現(xiàn)了
      List<E>, ---> 隊列接口
      RandomAccess, ---> 標記接口,支持隨機訪問
      Cloneable, ---> 標記訪問,支持拷貝
      java.io.Serializable ---> 標記接口,支持可序列化
      至于什么是標記接口,可以看我的這篇文章(為什么這些java接口沒有抽象方法?淺談Java標記接口  http://www.rzrgm.cn/jilodream/p/5986519.html)。
      (ps. 不知道大家發(fā)現(xiàn)沒有代表能力的接口jdk往往喜歡加-able,表示可以的。這在代碼簡潔之道這本書中也是推薦的命名方式之一。)

      我們接下來看下它的核心源碼。
      首先是元數(shù)據(jù)部分

          /**
           * The lock protecting all mutators.  (We have a mild preference
           * for builtin monitors over ReentrantLock when either will do.)
           */
          final transient Object lock = new Object();
      
          /** The array, accessed only via getArray/setArray. */
          private transient volatile Object[] array;

      定義了一個Object lock作為全局鎖。用final transient 來修飾。
      final 是為了防止鎖被亂改,導致出現(xiàn)安全問題。
      transient 則是表示在自動序列化時,不要主動來序列化該字段。這里也是了為了數(shù)據(jù)安全考慮的,防止正反序列化后,實例中的lock對象暴露出去,導致有安全問題。
      注意在JDK1.8中,鎖的實現(xiàn)使用的是ReentrantLock。但是在后續(xù)的版本中由于synchronized鎖的優(yōu)化,又使用了syncronized鎖。源碼中的注釋也有這樣寫:

          /**
           * The lock protecting all mutators.  (We have a mild preference
           * for builtin monitors over ReentrantLock when either will do.)
           */
           當內(nèi)置鎖和 ReentrantLock 都能用的時候,我們略微傾向于使用內(nèi)置鎖。

      定義了用來存放Object[] array;使用了transient volatile來修飾。
      使用transient 是為了解決序列化的問題。這里就會有一個疑問,為什么支持序列化,(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )并且又要在array 前邊修飾transient,防止它序列化?這是為何,我們序列化不就是為了轉(zhuǎn)存數(shù)據(jù)么?
      使用volatile修飾是為了保證array 發(fā)生變化時,所有直接使用的線程可以及時的感知到。這里需要對volatile有一定的認知才能明白,不太明白的同學請先了解
      首先是構(gòu)造方法:

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

      注意看CopyOnWriteArrayList是將一個len=0的數(shù)組初始化到容器中的。而并不像ArrayList一樣預留一個初始化大小的數(shù)組。這是為什么呢?

      然后是核心的讀方法:

          public E get(int index) {
              return elementAt(getArray(), index);
          }
          
              static <E> E elementAt(Object[] a, int index) {
              return (E) a[index];
          }

      很簡單,從指定的數(shù)組中直接讀出對應位置的元素,返回

      核心的四個寫方法:

      (1)隊列添加全新元素

          public boolean add(E e) {
              synchronized (lock) {
                  Object[] es = getArray();
                  int len = es.length;
                  es = Arrays.copyOf(es, len + 1);
                  es[len] = e;
                  setArray(es);
                  return true;
              }
          }

      1、寫方法直接加了一把全局鎖。
      2、然后在同步的代碼塊中,將元數(shù)據(jù)加入到一個(比當前數(shù)組長度+1長度的)新數(shù)組中。
      3、接著將新元素添加到隊列的末尾中。
      4、最后將新數(shù)組覆蓋到原數(shù)組中。
      5、由于原數(shù)組在定義時,使用了volatile關鍵字修飾。因此下次的讀操作就會立刻感知到該元素,

      (2)隊列指定位置添加全新元素

          public void add(int index, E element) {
              synchronized (lock) {
                  Object[] es = getArray();
                  int len = es.length;
                  if (index > len || index < 0)
                      throw new IndexOutOfBoundsException(outOfBounds(index, len));
                  Object[] newElements;
                  int numMoved = len - index;
                  if (numMoved == 0)
                      newElements = Arrays.copyOf(es, len + 1);
                  else {
                      newElements = new Object[len + 1];
                      System.arraycopy(es, 0, newElements, 0, index);
                      System.arraycopy(es, index, newElements, index + 1,
                                       numMoved);
                  }
                  newElements[index] = element;
                  setArray(newElements);
              }
          }

      1、寫方法直接加了一把全局鎖。
      2、然后判斷當前添加位置的索引是否合法超出了len的合理范圍,如果是,那么就拋出異常。
      3、然后判斷當前添加位置是不是數(shù)組的最后位置:
      <1>如果是的話,就把原數(shù)組的所有元素拷貝到新數(shù)組的中。新數(shù)組的長度等于老數(shù)組+1。
      <2>如果不是的話,就直接new 一個長度為len+1的數(shù)組。然后分別把老數(shù)組的前后兩部分拷貝到新數(shù)組中。
      4、將添加位置的元素設置為新元素。
      (3)移出指定位置的元素

          public E remove(int index) {
              synchronized (lock) {
                  Object[] es = getArray();
                  int len = es.length;
                  E oldValue = elementAt(es, index);
                  int numMoved = len - index - 1;
                  Object[] newElements;
                  if (numMoved == 0)
                      newElements = Arrays.copyOf(es, len - 1);
                  else {
                      newElements = new Object[len - 1];
                      System.arraycopy(es, 0, newElements, 0, index);
                      System.arraycopy(es, index + 1, newElements, index,
                                       numMoved);
                  }
                  setArray(newElements);
                  return oldValue;
              }
          }

      1、移出方法首先加入一把全局鎖
      2、獲取指定位置的元素
      3、判斷移出元素是不是隊列的末尾:
      如果是的話,(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )就把原數(shù)組的去除末尾元素的部分拷貝到新數(shù)組的中。新數(shù)組的長度等于老數(shù)組-1。
      如果不是的話,就直接new 一個長度為len-1的數(shù)組。然后分別把老數(shù)組的前后兩部分拷貝到新數(shù)組中。
      4、返回查詢到的指定元素

      (4)移出指定元素

         public boolean remove(Object o) {
              Object[] snapshot = getArray();
              int index = indexOfRange(o, snapshot, 0, snapshot.length);
              return index >= 0 && remove(o, snapshot, index);
          }
      
          private boolean remove(Object o, Object[] snapshot, int index) {
              synchronized (lock) {
                  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]
                              && Objects.equals(o, current[i])) {
                              index = i;
                              break findIndex;
                          }
                      }
                      if (index >= len)
                          return false;
                      if (current[index] == o)
                          break findIndex;
                      index = indexOfRange(o, current, index, len);
                      if (index < 0)
                          return false;
                  }
                  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;
              }
          }
          

      1、先獲取當前元素的位置
      2、獲取元素對應的位置(注意如果有多個相同的元素這里只取第一個找到的元素o),我們記為index
      3、然后跳轉(zhuǎn)到另外一個remove 重載的方法中。
      4、在重載方法中加入全局鎖
      5、取出最新的數(shù)組current
      6、判斷新數(shù)組的current和之前查找時用的數(shù)組是不是一個數(shù)組:
      <1>如果不是,說明有其他線程并發(fā)寫操作了,此時開始重新找。
      <2>首先在剛才index之前的元素都找一遍,如果找到,說明前移了,或者在index的更前方又插入該元素,此時這個新位置就是要移除的值。
      <3>如果沒找到又判斷index>=新數(shù)組長度,那就說明是沒有這個元素o了,直接就返回false了(因為上一步找的是index的位置,如果index都>=新數(shù)組長度,那么其實就已經(jīng)遍歷了)。
      <4>如果還沒有,那么判斷index的位置是否仍等于元素o,如果是那么也判斷找到了。
      <5>如果還沒有就判斷index后邊的位置是否有元素等于o
      這里其實相當于分了3部分,如果發(fā)生了寫操作就判斷 index之前,index的位置,index之后,這三塊分別有沒有o出現(xiàn)。
      7、直接new 一個長度為len-1的數(shù)組。然后分別把老數(shù)組的前后兩部分拷貝到新數(shù)組中。

      最后這個remove方法其實看的人覺得很繁瑣,為什么他沒有直接進來一把全局鎖,然后遍歷查找最后生成新數(shù)組即可。其實我覺得是可以的,jdk1.7也的確是這樣的。但是在后續(xù)的版本中將預查找的部分移出了鎖的范圍。
      鎖的粒度更小。對寫的操作更友好。

      以上就是CopyOnWriteArrayList的內(nèi)部核心實現(xiàn)了。
      現(xiàn)在又回到最初的問題。(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )為什么元數(shù)組array的len始終和當前存儲的元素數(shù)保持一致。而不是像arraylist等其他容器,給一定的預留空間方便,方便寫呢?
      我認為主要基于以下考慮:

      CopyOnWriteArrayList本質(zhì)是為了讀寫分離更準確的說是為了讀多寫少的場景設計的。盡可能的壓縮數(shù)組也為數(shù)組拷貝提供了優(yōu)勢。
      array是及時變量,每次使用都是全新new一個出來,并不是在原有基礎上寫,沒有必要為了性能預留空間。(核心原因)

      另外的一個疑問為什么支持序列化又要在array 前邊修飾transient,防止它序列化?這是為何,我們序列化不就是為了轉(zhuǎn)存數(shù)據(jù)么?
      這主要是由于CopyOnWriteArrayList是使用寫單獨拷貝的方式來處理并發(fā)的。而array前邊又有volatile修飾,也就是說array一旦發(fā)生變化,那么所有使用的線程在下次使用時就會立刻感知到。倘若我們序列化進行中,突然發(fā)生了寫操作,并已經(jīng)完成,我是應該接著序列化(此時就有錯誤),還是重新序列化(性能太差)?
      因此又單獨實現(xiàn)了該方法配合序列化用:

         private void writeObject(java.io.ObjectOutputStream s)
              throws java.io.IOException {
      
              s.defaultWriteObject();
      
              Object[] es = getArray();
              // Write out array length
              s.writeInt(es.length);
      
              // Write out all elements in the proper order.
              for (Object element : es)
                  s.writeObject(element);
          }

      也就是我單獨取出一份當前的數(shù)組放到es中,后邊不管容器怎么寫,我只序列化es中數(shù)據(jù)。

      除此之外,隊列的迭代也會有類似的問題。
      我們知道隊列的迭代,本質(zhì)上就是使用迭代器依次的迭代元數(shù)據(jù)。而在CopyOnWriteArrayList中,和序列化相似,在迭代時,是將當前的元數(shù)組的引用指向到snapshot變量中。后續(xù)的迭代操作都是對該snapshot來操作的。
      也就是說,在并發(fā)迭代時,我們迭代的數(shù)據(jù)是當前緩存的歷史快照數(shù)據(jù)。如下:

          public Iterator<E> iterator() {
              return new COWIterator<E>(getArray(), 0);
          }
          
          static final class COWIterator<E> implements ListIterator<E> {
              /** Snapshot of the array */
              private final Object[] snapshot;
              /** Index of element to be returned by subsequent call to next.  */
              private int cursor;
      
              COWIterator(Object[] es, int initialCursor) {
                  cursor = initialCursor;
                  snapshot = es;
              }
          ....
          }

      可以看這個例子:

      public class CopyOnWriteArrayListLearn {
          public static void main(String[] args) {
              CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList();
              list.add(1);
              list.add(2);
              list.add(3);
              list.add(4);
              for (Integer item : list) {
                  list.add(item * 10);
                  System.out.println("item:" + item);
              }
          }
      }

      輸出如下,只輸出了迭代開啟一瞬間的容器中的元素:

      Connected to the target VM, address: '127.0.0.1:61377', transport: 'socket'
      item:1
      item:2
      item:3
      item:4
      Disconnected from the target VM, address: '127.0.0.1:61377', transport: 'socket'

      同時,直接使用迭代器進行寫操作時,(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )就會因為是快照數(shù)據(jù)而沒有意義,所以源碼中直接將迭代器的寫操作拋出異常了:

              public void remove() {
                  throw new UnsupportedOperationException();
              }
      
              /**
               * Not supported. Always throws UnsupportedOperationException.
               * @throws UnsupportedOperationException always; {@code set}
               *         is not supported by this iterator.
               */
              public void set(E e) {
                  throw new UnsupportedOperationException();
              }
      
              /**
               * Not supported. Always throws UnsupportedOperationException.
               * @throws UnsupportedOperationException always; {@code add}
               *         is not supported by this iterator.
               */
              public void add(E e) {
                  throw new UnsupportedOperationException();
              }

       

      posted @ 2025-10-11 10:40  王若伊_恩賜解脫  閱讀(220)  評論(2)    收藏  舉報

      主站蜘蛛池模板: 日韩人妖精品一区二区av| 国产精品国产三级国快看| 午夜一区欧美二区高清三区| 在线高清免费不卡全码| 少妇激情一区二区三区视频小说| japanese边做边乳喷| 国产亚洲一区二区三区成人| a男人的天堂久久a毛片| 国产免费无遮挡吸奶头视频| 我要看特黄特黄的亚洲黄片| 人人爽亚洲aⅴ人人爽av人人片| 久久人人97超碰人人澡爱香蕉| 亚洲熟妇乱色一区二区三区| 国产免费无遮挡吸奶头视频| 久久久久国产精品人妻| 亚洲精品一区二区18禁| 亚洲色最新高清AV网站| 日本伊人色综合网| 国产激情福利短视频在线| 乱妇乱女熟妇熟女网站| 国日韩精品一区二区三区| 久久久久久综合网天天| 国产一二三区在线| 成人亚欧欧美激情在线观看| 国产精品精品一区二区三| 欧美视频网站www色| 曰韩无码av一区二区免费| 日韩一区二区三区无码a片| 少妇无套内射中出视频| 久久亚洲欧美日本精品| 老色批国产在线观看精品| 男女啪啪免费观看网站| 免费极品av一视觉盛宴| 性一交一乱一伦| 风流老熟女一区二区三区| 中文字幕亚洲综合小综合| 成 人色 网 站 欧美大片| 国产又黄又硬又粗| 国产女同一区二区在线| 亚洲精品一区二区美女| 国产精品高清视亚洲精品|