xylz,imxylz

          關注后端架構、中間件、分布式和并發編程

             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

          本小節是《并發容器》的最后一部分,這一個小節描述的是針對List/Set接口的一個線程版本。

          在《并發隊列與Queue簡介》中介紹了并發容器的一個概括,主要描述的是Queue的實現。其中特別提到一點LinkedList是List/Queue的實現,但是LinkedList確實非線程安全的。不管BlockingQueue還是ConcurrentMap的實現,我們發現都是針對鏈表的實現,當然盡可能的使用CAS或者Lock的特性,同時都有通過鎖部分容器來提供并發的特性。而對于List或者Set而言,增、刪操作其實都是針對整個容器,因此每次操作都不可避免的需要鎖定整個容器空間,性能肯定會大打折扣。要實現一個線程安全的List/Set,只需要在修改操作的時候進行同步即可,比如使用java.util.Collections.synchronizedList(List<T>)或者java.util.Collections.synchronizedSet(Set<T>)。當然也可以使用Lock來實現線程安全的List/Set。

          通常情況下我們的高并發都發生在“多讀少寫”的情況,因此如果能夠實現一種更優秀的算法這對生產環境還是很有好處的。ReadWriteLock當然是一種實現。CopyOnWriteArrayList/CopyOnWriteArraySet確實另外一種思路。

          CopyOnWriteArrayList/CopyOnWriteArraySet的基本思想是一旦對容器有修改,那么就“復制”一份新的集合,在新的集合上修改,然后將新集合復制給舊的引用。當然了這部分少不了要加鎖。顯然對于CopyOnWriteArrayList/CopyOnWriteArraySet來說最大的好處就是“讀”操作不需要鎖了。

          我們來看看源碼。

          /** The array, accessed only via getArray/setArray. */
          private volatile transient Object[] array;
          public E get(int index) {
             
          return (E)(getArray()[index]);
          }
          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;
          }
          public Iterator<E> iterator() {
             
          return new COWIterator<E>(getArray(), 0);
          }
             
          public void clear() {
             
          final ReentrantLock lock = this.lock;
              lock.lock();
             
          try {
                  setArray(
          new Object[0]);
              }
          finally {
                  lock.unlock();
              }
              }

          對于上述代碼,有幾點說明:

          1. List仍然是基于數組的實現,因為只有數組是最快的。
          2. 為了保證無鎖的讀操作能夠看到寫操作的變化,因此數組array是volatile類型的。
          3. get/indexOf/iterator等操作都是無鎖的,同時也可以看到所操作的都是某一時刻array的鏡像(這得益于數組是不可變化的)
          4. add/set/remove/clear等元素變化的都是需要加鎖的,這里使用的是ReentrantLock。

          這里有一段有意思的代碼片段。

              public E set(int index, E element) {
             
          final ReentrantLock lock = this.lock;
              lock.lock();
             
          try {
                  Object[] elements
          = getArray();
                  Object oldValue
          = elements[index];
                 
          if (oldValue != element) {
                 
          int len = elements.length;
                  Object[] newElements
          = Arrays.copyOf(elements, len);
                  newElements[index]
          = element;
                  setArray(newElements);
                  }
          else {
                 
          // Not quite a no-op; ensures volatile write semantics
                  setArray(elements);
                  }
                 
          return (E)oldValue;
              }
          finally {
                  lock.unlock();
              }
              }

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

          對于set操作,如果元素有變化,修改后setArray(newElements);將新數組賦值還好理解。那么如果一個元素沒有變化,也就是上述代碼的else部分,為什么還需要進行一個無謂的setArray操作?畢竟setArray操作沒有改變任何數據。

          對于這個問題也是很有意思,有一封郵件討論了此問題(123)。
          大致的意思是,盡管沒有改變任何數據,但是為了保持“volatile”的語義,任何一個讀操作都應該是一個寫操作的結果,也就是讀操作看到的數據一定是某個寫操作的結果(盡管寫操作沒有改變數據本身)。所以這里即使不設置也沒有問題,僅僅是為了一個語義上的補充(個人理解)。

          這里還有一個有意思的討論,說什么addIfAbsent在元素沒有變化的時候為什么沒有setArray操作?這個要看怎么理解addIfAbsent的語義了。如果說addIfAbsent語義是”寫“或者”不寫“操作,而把”不寫“操作當作一次”讀“操作的話,那么”讀“操作就不需要保持volatile語義了。

           

          對于CopyOnWriteArraySet而言就簡單多了,只是持有一個CopyOnWriteArrayList,僅僅在add/addAll的時候檢測元素是否存在,如果存在就不加入集合中。

          private final CopyOnWriteArrayList<E> al;
          /**
          * Creates an empty set.
          */
          public CopyOnWriteArraySet() {
              al
          = new CopyOnWriteArrayList<E>();
          }

          public boolean add(E e) {
             
          return al.addIfAbsent(e);
          }

           

          在使用上CopyOnWriteArrayList/CopyOnWriteArraySet就簡單多了,和List/Set基本相同,這里就不再介紹了。

           

          整個并發容器結束了,接下來好好規劃下線程池部分,然后進入最后一部分的梳理。

           



          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2010-11-23 22:22 imxylz 閱讀(14702) 評論(1)  編輯  收藏 所屬分類: Java Concurrency

          評論

          # re: 深入淺出 Java Concurrency (27): 并發容器 part 12 線程安全的List/Set 2013-07-31 13:59 是的方式的
          是地方是的  回復  更多評論
            


          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 如皋市| 资中县| 绥德县| 青阳县| 高唐县| 涟源市| 赣榆县| 楚雄市| 霍邱县| 依兰县| 宁明县| 沧州市| 上思县| 河西区| 正镶白旗| 米易县| 酉阳| 大名县| 翼城县| 随州市| 鹤峰县| 大足县| 渑池县| 祥云县| 巩义市| 临海市| 平阳县| 太仆寺旗| 南木林县| 黎城县| 乌什县| 弥渡县| 邹平县| 九龙城区| 嵩明县| 南城县| 保亭| 赤城县| 宁乡县| 徐闻县| 施秉县|