莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Clojure的transient集合

          Posted on 2010-08-18 18:46 dennis 閱讀(2289) 評論(0)  編輯  收藏 所屬分類: Clojure
              下班后記一些有趣的東西。

              這個話題是阿寶同學(xué)問我為什么clojure的PersistentVector的節(jié)點(diǎn)Node為什么要有個原子引用指向一個線程:
          static class Node implements Serializable {
              
          //記錄的線程
              transient final AtomicReference<Thread> edit;
              
          final Object[] array;

              Node(AtomicReference
          <Thread> edit, Object[] array){
                  
          this.edit = edit;
                  
          this.array = array;
              }

              Node(AtomicReference
          <Thread> edit){
                  
          this.edit = edit;
                  
          this.array = new Object[32];
              }
          }

              我還真不懂,沒有細(xì)看過這部分代碼,早上花點(diǎn)時間學(xué)習(xí)了下。
              PersistentVector的實(shí)現(xiàn)是另一個話題,這里不提。我們都知道clojure的數(shù)據(jù)結(jié)構(gòu)是immutable的,修改任意一個數(shù)據(jù)結(jié)構(gòu)都將生成一個新的數(shù)據(jù)結(jié)構(gòu),原來的不變。為了減少復(fù)制的開銷,clojure的數(shù)據(jù)結(jié)構(gòu)同時是persistent,所謂持久數(shù)據(jù)結(jié)構(gòu)是將數(shù)據(jù)組織為樹形的層次結(jié)構(gòu),修改的時候只是root改變,指向不同的節(jié)點(diǎn),原有的節(jié)點(diǎn)仍然復(fù)用,從而避免了大量的數(shù)據(jù)復(fù)制,具體可以搜索下ideal hash trees這篇paper, paper難懂,可以看看這篇blog

              但是在創(chuàng)建PersistentVector的時候,從一堆現(xiàn)有的元素或者集合創(chuàng)建一個PersistentVector,如果每次都重新生成一個PersistentVector,未免太浪費(fèi),創(chuàng)建過程的性能會受到影響。我們完全可以假設(shè)創(chuàng)建PersistentVector這個過程肯定是線程安全的,沒有必要每添加一個元素就重新生成一個PersistentVector,完全可以在同一個PersistentVector上修改。這就是TransientVector的意義所在。
              TransientVector就是一個可修改的Vector,調(diào)用它添加一個元素,刪除一個元素,都是在同一個對象上進(jìn)行,而不是生成新的對象。查看PersistentVector的創(chuàng)建:

          static public PersistentVector create(ISeq items){
              TransientVector ret 
          = EMPTY.asTransient();
              
          for(; items != null; items = items.next())
                  ret 
          = ret.conj(items.first());
              
          return ret.persistent();
          }

          static public PersistentVector create(List items){
              TransientVector ret 
          = EMPTY.asTransient();
              
          for(Object item : items)
                  ret 
          = ret.conj(item);
              
          return ret.persistent();
          }

          static public PersistentVector create(Object items){
              TransientVector ret 
          = EMPTY.asTransient();
              
          for(Object item : items)
                  ret 
          = ret.conj(item);
              
          return ret.persistent();
          }


              看到三個方法的第一步都是將EMPTY集合transient化,生成一個可修改的TransientVector:
          TransientVector(PersistentVector v){
                  
          this(v.cnt, v.shift, editableRoot(v.root), editableTail(v.tail));
              }

          static Node editableRoot(Node node){
                  
          return new Node(new AtomicReference<Thread>(Thread.currentThread()), node.array.clone());
              }

              生成的時候記錄了當(dāng)前的線程在root節(jié)點(diǎn)。然后添加元素的時候直接調(diào)用TransientVector的conj方法,查看conj可以看到每次返回的都是this:
          public TransientVector conj(Object val){
                          
          //確保修改過程合法
                  ensureEditable();
                   
          //忽略邏輯
                  return this;
              }

              查看ensureEditable方法:
          void ensureEditable(){
                  Thread owner 
          = root.edit.get();
                  
          if(owner == Thread.currentThread())
                      
          return;
                  
          if(owner != null)
                      
          throw new IllegalAccessError("Transient used by non-owner thread");
                  
          throw new IllegalAccessError("Transient used after persistent! call");
              }


              終于看到Node中的edit引用的線程被使用了,判斷當(dāng)前修改的線程是否是使得集合transient化的線程,如果不是則拋出異常,這是為了保證對TransientVector的編輯是在同一個線程里,防止因?yàn)橐馔獍l(fā)布TransientVector引用引起的線程安全隱患。

              知道了transient集合的用途,我們能在clojure中使用嗎?完全沒問題,clojure.core有個transient方法,可以將一個集合transient化:
          (defn transient 
            [
          ^clojure.lang.IEditableCollection coll] 
            (.asTransient coll))
            
              前提是這個集合是可編輯的,clojure的map、vector和set都是可編輯的。讓我們確認(rèn)下transient修改后的集合還是不是自身:
          user=> (def v1 [1 2 3])
          #
          'user/v1
          user=> (def v2 (transient v1))
          #
          'user/v2
          user=> v2
          #
          <TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

              定義了集合v1,v2是調(diào)用了transient之后的集合,查看v2,果然是一個TransientVector。查看v2的元素個數(shù)是不是3個:
          user=> (.count v2)
          3

              沒問題,注意,我們不能直接調(diào)用count函數(shù),因?yàn)関2是個普通的java對象,我們必須使用dot操作符來調(diào)用java對象的方法。添加一個元素看看:
          user=> (def v3 (.conj v2 4))
          #
          'user/v3
          user=> v3
          #
          <TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

              添加一個元素后形成集合v3,查看v3,跟v2是同一個對象#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>
          。證明了transient集合修改的是自身,而不是生成一個新集合。確認(rèn)下4有加入v2和v3:
          user=> (.nth v3 3)
          4
          user
          => (.count v2)
          4
          user
          => (.count v3)
          4
          user
          => (.nth v2 3)
          4

              果然沒有問題。transient集合的使用應(yīng)當(dāng)慎重,除非能確認(rèn)沒有其他線程會去修改集合,并且對線程的可見性要求不高的時候,也許可以嘗試下這個技巧。
            
          主站蜘蛛池模板: 石门县| 平武县| 新巴尔虎右旗| 临夏县| 织金县| 临沭县| 馆陶县| 武义县| 江孜县| 搜索| 如皋市| 湛江市| 平泉县| 清水河县| 无锡市| 横峰县| 瓦房店市| 铜山县| 交口县| 永年县| 景泰县| 炉霍县| 资阳市| 池州市| 和硕县| 托克逊县| 蒙自县| 东阿县| 石景山区| 扎囊县| 乌拉特后旗| 旌德县| 饶平县| 平山县| 西峡县| 嘉禾县| 湟中县| 尉犁县| 隆化县| 宁城县| 镶黄旗|