知是行的主意,行是知的功夫。这篇文章主要讲述Netty之DefaultAttributeMap与AttributeKey的机制和原理相关的知识,希望能为你提供帮助。
一、介绍和原理分析
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
相同的原子操作;- 数组和链表都是线程安全的;
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
(旧值)返回讲到这里,那么在多线程环境下,常量池和哈希表是共享的吗?
答案当然是肯定的!
那多线程环境下只存在一个线程池和哈希表嘛?
【Netty之DefaultAttributeMap与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
三、结束语评论区可留言,可私信,可互相交流学习,共同进步,欢迎各位给出意见或评价,本人致力于做到优质文章,希望能有幸拜读各位的建议!
个人博客园:https://www.cnblogs.com/fyphome
推荐阅读
- 如何解析EML(邮件)格式的文件以及一款小巧的EML邮件阅读工具
- OpenHarmony啃论文成长计划——浅谈中间件
- 官宣!全新「悟空熊」周边上线,1200份周边免费送
- 开发者如何调整设计系统以适用于元宇宙()
- Win10/11保存图片默认jfif格式怎么改成jpg?如何一键批量修改所有图片格式()
- HashMap底层原理及jdk1.8源码解读吐血整理1.3w字长文
- 大数据分布式计算系统 Spark 入门核心之 RDD
- k8s集群Job Pod 容器可能因为多种原因失效,想要更加稳定的使用Job负载,有哪些需要注意的地方()
- 除了关注业务连续性,运维还能创造什么价值()