一、介绍和原理分析
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
的字段就没有详细画出来
graph LR
subgraph DefaultAttributeMap
subgraph \"AtomicReferenceArray(数组)\"
end
end
数组默认创建大小为4,如下图所示
graph LR
subgraph \"AtomicReferenceArray\"
subgraph \"下标1\"
head1(head)
end
subgraph \"下标2\"
head2(head)
end
subgraph \"下标3\"
head3(head)
end
subgraph \"下标4\"
head4(head)
end
head1 --> next1(next)
head2 --> next2(next)
head3 --> next3(next)
head4 --> next4(next)
next1--> next1-1(next)
next2--> next2-1(next)
next3--> next3-1(next)
next4--> next4-1(next)
end
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
来源:https://www.cnblogs.com/fyphome/p/16054251.html
本站部分图文来源于网络,如有侵权请联系删除。