下班后記一些有趣的東西。
這個話題是阿寶同學(xué)問我為什么clojure的PersistentVector的節(jié)點(diǎn)Node為什么要有個原子引用指向一個線程:
我還真不懂,沒有細(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:
生成的時候記錄了當(dāng)前的線程在root節(jié)點(diǎn)。然后添加元素的時候直接調(diào)用TransientVector的conj方法,查看conj可以看到每次返回的都是this:
查看ensureEditable方法:
終于看到Node中的edit引用的線程被使用了,判斷當(dāng)前修改的線程是否是使得集合transient化的線程,如果不是則拋出異常,這是為了保證對TransientVector的編輯是在同一個線程里,防止因?yàn)橐馔獍l(fā)布TransientVector引用引起的線程安全隱患。
知道了transient集合的用途,我們能在clojure中使用嗎?完全沒問題,clojure.core有個transient方法,可以將一個集合transient化:
前提是這個集合是可編輯的,clojure的map、vector和set都是可編輯的。讓我們確認(rèn)下transient修改后的集合還是不是自身:
定義了集合v1,v2是調(diào)用了transient之后的集合,查看v2,果然是一個TransientVector。查看v2的元素個數(shù)是不是3個:
沒問題,注意,我們不能直接調(diào)用count函數(shù),因?yàn)関2是個普通的java對象,我們必須使用dot操作符來調(diào)用java對象的方法。添加一個元素看看:
添加一個元素后形成集合v3,查看v3,跟v2是同一個對象#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>
。證明了transient集合修改的是自身,而不是生成一個新集合。確認(rèn)下4有加入v2和v3:
果然沒有問題。transient集合的使用應(yīng)當(dāng)慎重,除非能確認(rèn)沒有其他線程會去修改集合,并且對線程的可見性要求不高的時候,也許可以嘗試下這個技巧。
這個話題是阿寶同學(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];
}
}
//記錄的線程
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

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());
}
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();
//忽略邏輯
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");
}
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.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>
#'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
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>
#'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
4
user=> (.count v2)
4
user=> (.count v3)
4
user=> (.nth v2 3)
4
果然沒有問題。transient集合的使用應(yīng)當(dāng)慎重,除非能確認(rèn)沒有其他線程會去修改集合,并且對線程的可見性要求不高的時候,也許可以嘗試下這個技巧。