一、介紹和原理分析
1.什麼是 DefaultAttributeMap?
DefaultAttributeMap
是一個 陣列 + 連結串列
結構的執行緒安全Map
。
2.什麼是 AttributeKey?
AttributeKey
可以想象成一個快取set
,存放了一組key
的集合,與DefaultAttributeMap
之間的關係是,後者中的雜湊圖
存放鍵值對(k-v
)的v
即是AttributeKey
。
有了AttributeKey
,你自然會想到Attribute
,兩者之間又有什麼關係呢?下面會講,慢慢理解,跟著我思路!
3. 什麼是 Attribute?
Attribute
顧名思義,就是與AttributeKey
是一對的,形象一點說就是你跟你的物件(老婆),而你就是key
,是一對一的,不能是一對多的關係
憑什麼是一對一,也就是憑什麼你只能有一個物件?
AttributeKey
它受DefaultAttributeMap
中的內部類DefaultAttribute
約束,前面說了DefaultAttributeMap
的結構是以陣列和連結串列的形式,其實它的最小單元(結點)就是DefaultAttribute
。
4. 關於陣列和連結串列的結構
- 陣列採用的是
AtomicReferenceArray
, 連結串列 中 節點為DefaultAttribute
結構; DefaultAttribute
繼承了AtomicReference
,所以也是具有與AtomicReference
相同的原子操作;- 陣列和連結串列都是執行緒安全的;
5. DefaultAttributeMap 與 AtomicReferenceArray 的關係圖
其中,每個結點DefaultAttribute
的欄位就沒有詳細畫出來
陣列預設建立大小為4,如下圖所示
6. valueOf("key")原理
預設情況下,第一次存放key
值時,一般使用 AttributeKey.valueOf("rpcResponse")
,此時在AttributeKey
中的常量池會隨之建立,並初始化好ConcurrentHashMap
,下面通過原始碼追蹤
使用AttributeKey
的靜態方法valueOf("key")
public final class AttributeKey<T> extends AbstractConstant<AttributeKey<T>> {
// static final 修飾的 引用型別在 類初始化階段 就已經完成
//簡單使用AttributeKey不會觸發類初始化,訪問了靜態方法valueOf()導致了初始化
private static final ConstantPool<AttributeKey<Object>> pool = new ConstantPool<AttributeKey<Object>>() {
}
pool
已被例項化,類中的屬性也會例項化
public abstract class ConstantPool<T extends Constant<T>> {
private final ConcurrentMap<String, T> constants = PlatformDependent.newConcurrentHashMap();
private final AtomicInteger nextId = new AtomicInteger(1);
而.valueOf("rpcResponse")
該方法呼叫後,會先去new
一個AbstractConstant
物件,優先對它的id
值和name
值(傳進的key
)進行初始化
public class ChannelOption<T> extends AbstractConstant<ChannelOption<T>> {
protected ChannelOption<Object> newConstant(int id, String name) {
return new ChannelOption(id, name);
}
// 省略幾行
private ChannelOption(int id, String name) {
super(id, name);
}
}
在ConcurrentHashMap
中呼叫putIfAbsent
方法將key
值存入,方法是為空才放入的意思,每次都會返回一個初始化id
和key
值的AbstractConstant
private T getOrCreate(String name) {
T constant = (Constant)this.constants.get(name);
if (constant == null) {
// new 完後 返回給 tempConstant
T tempConstant = this.newConstant(this.nextId(), name);
constant = (Constant)this.constants.putIfAbsent(name, tempConstant);
if (constant == null) {
return tempConstant;
}
}
return constant;
}
最後強制轉換成了AttributeKey
並返回
public static <T> AttributeKey<T> valueOf(String name) {
return (AttributeKey)pool.valueOf(name);
}
下次再使用valueOf("")傳入引數時,如果引數相同,會去拿AttributeKey
(舊值)返回
講到這裡,那麼在多執行緒環境下,常量池和雜湊表是共享的嗎?
答案當然是肯定的!
那多執行緒環境下只存在一個執行緒池和雜湊表嘛?
答案也是明確的,staic final
修飾的變數,是在類載入階段完成的,虛擬機器會保證執行緒安全
7. newInstance 原理
newInstance
與 valueOf
的 原理 異常類似,都是樂觀鎖的思想,只是 在多執行緒環境下前者要 丟擲 異常(不太準確,後面總結會糾正),後者直接返回同一個
public T newInstance(String name) {
checkNotNullAndNotEmpty(name);
return this.createOrThrow(name);
}
newInstance
呼叫的方法是 常量池中的 createOrThrow
,而 valueOf 呼叫的方法是 getOrCreate
private T createOrThrow(String name) {
T constant = (Constant)this.constants.get(name);
// putIfAbsent 方法執行完畢後,其他執行緒將會直接丟擲異常
if (constant == null) {
T tempConstant = this.newConstant(this.nextId(), name);
// 多執行緒環境下,多個執行緒能夠進入這裡
constant = (Constant)this.constants.putIfAbsent(name, tempConstant);
// 不過 在 後執行 putIfAbsent 的執行緒,會先 阻塞在該方法中的 sychronized 同步程式碼塊中
// 也有 先 返回的 執行緒,return null,會去直接拿到 tempConstant,與 return 的地址 是
//同一個
if (constant == null) {
return tempConstant;
}
}
throw new IllegalArgumentException(String.format("'%s' is already in use", name));
}
8. ctx.channel().attr(key).set(T object)與 get() 原理:
首先是先操作ctx.channel().attr(key)
,返回的值型別為Attribute
,使用的attr
方法,是因為Channel
繼承了AttributeMap
,呼叫的方法實際上是對實現類DefaultAttributeMap
中實現方法的呼叫
原始碼雖然篇幅有點長,但其實不難理解,原始碼用的版本是netty-all-4.1.20.Final
public <T> Attribute<T> attr(AttributeKey<T> key) {
if (key == null) {
throw new NullPointerException("key");
} else {
AtomicReferenceArray<DefaultAttributeMap.DefaultAttribute<?>> attributes = this.attributes;
if (attributes == null) {
attributes = new AtomicReferenceArray(4);
if (!updater.compareAndSet(this, (Object)null, attributes)) {
attributes = this.attributes;
}
}
/** index 是 取出 key 的 id 值 與 3 與 運算,3是因為建立陣列預設就是3
* 這裡由於 key 的 id 值 是 加1 增長的,所以 每次 都是 類似於 雜湊演算法的
* %3 來命中槽位
*/
int i = index(key);
DefaultAttributeMap.DefaultAttribute<?> head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
//該 下標 未使用,也就是 還沒有頭結點,需先 初始化 頭結點
if (head == null) {
// 頭結點不會 存入 key 值
head = new DefaultAttributeMap.DefaultAttribute();
// key 值 存入到 了 欄位 key 中,見下一個程式碼段
DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
head.next = attr;
attr.prev = head;
if (attributes.compareAndSet(i, (Object)null, head)) {
return attr;
}
head = (DefaultAttributeMap.DefaultAttribute)attributes.get(i);
}
// 這裡要做 執行緒安全,因為只有原子操作是執行緒安全,但原子組合操作就不是執行緒安全的了
synchronized(head) {
DefaultAttributeMap.DefaultAttribute curr = head;
/**
* 直到找到 key 值 相同 的結點,否則 遍歷到 尾結點,沒有找到則
* 通過 尾插入 新節點 再將其返回
*/
while(true) {
DefaultAttributeMap.DefaultAttribute<?> next = curr.next;
if (next == null) {
DefaultAttributeMap.DefaultAttribute<T> attr = new DefaultAttributeMap.DefaultAttribute(head, key);
curr.next = attr;
attr.prev = curr;
return attr;
}
if (next.key == key && !next.removed) {
return next;
}
curr = next;
}
}
}
}
一個有效結點只跟一個AttributeKey
繫結,不包括head
頭結點,下面引數2
作為了key
值傳入建構函式,接著返回型別為DefaultAttribute
的結點
DefaultAttribute(DefaultAttributeMap.DefaultAttribute<?> head, AttributeKey<T> key) {
this.head = head;
this.key = key;
}
返回的結點型別就是前面說的Attribute
,但該結點沒有value
屬性,又是怎麼存進去的呢?對set()
方法通過原始碼追蹤
其實該節點DefaultAttribute
繼承了AtomicReference
private static final class DefaultAttribute<T> extends AtomicReference<T> implements Attribute<T> {
}
使得結點多了一個value
欄位,形象來說,就是你已經跟你物件結合在了一起,一個節點的key
對應著一個value
了,都在同一個DefaultAttribute
類中
public class AtomicReference<V> implements java.io.Serializable {
private static final VarHandle VALUE;
}
get()
原理 與 set()
方法一樣,不再贅述
二、總結
1. valueOf
可以看出最關鍵的方法是 getOrCreate
,這個方法最大的特點是採用類樂觀鎖的方式,當我們最後發現了 constant != null
時,那麼我們返回已經插入的 constant
。
2. newInstance
可以看出最關鍵的方法是 createOrThrow
,這個方法最大的特點是採用類樂觀鎖的方式,當我們最後發現了 constant != null
時,我們直接丟擲異常。
3. valueOf和newInstance 對比
valueOf
:如果 name
為null
、空字串時丟擲異常,不存在就建立一個,且多執行緒隨先建立返回誰。
newInstance
: 如果name
為null
、空字串或存在時,就丟擲異常,且多執行緒建立,第一個成功建立後,其他能判斷到第一個if
裡面的的幾個執行緒返回建立值,其他執行緒丟擲異常。
借鑑:
簡書:https://www.jianshu.com/p/e7d9a2e8c0ac
官方文件:https://netty.io/4.1/api/index.html
三、結束語
評論區可留言,可私信,可互相交流學習,共同進步,歡迎各位給出意見或評價,本人致力於做到優質文章,希望能有幸拜讀各位的建議!
與51cto同步:https://blog.51cto.com/fyphome
與csdn同步:https://blog.csdn.net/F15217283411
專注品質,熱愛生活。
交流技術,尋求同志。
—— 延年有餘 QQ:1160886967