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相同的原子操作;- 数组和链表都是线程安全的;
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/u_15409831
与csdn同步:https://blog.csdn.net/F15217283411
专注品质,热爱生活。
交流技术,寻求同志。
—— 延年有余 QQ:1160886967
Netty之DefaultAttributeMap与AttributeKey的机制和原理的更多相关文章
- Netty源码解析 -- 事件循环机制实现原理
本文主要分享Netty中事件循环机制的实现. 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(EventLoop)处理IO事 ...
- logrotate机制与原理[转载]
http://blog.lightxue.com/how-logrotate-works/ 日志实在是太有用了,它记录了程序运行时各种信息.通过日志可以分析用户行为,记录运行轨迹,查找程序问题.可惜磁 ...
- Android(java)学习笔记202:Handler消息机制的原理和实现
联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系 1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...
- Java序列化的机制和原理
Java序列化的机制和原理 本文讲解了Java序列化的机制和原理.从文中你可以了解如何序列化一个对象,什么时候需要序列化以及Java序列化的算法. 有关Java对象的序列化和反序列化也算是Java基础 ...
- Java序列化机制和原理及自己的理解
Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一 ...
- Session执行机制与原理
Session执行机制与原理 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 什么是Session 首先了解一下Session的中文意思:一次会话,什么是一次会话呢?我举个例子:就我们现 ...
- Qt核心机制与原理
转: https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...
- 为什么要有handler机制?handler机制的原理
为什么要有handler机制? 在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过 ...
- Java序列化机制和原理
Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一 ...
随机推荐
- Typora软件的使用
Typora软件 一.简介 1.该软件编写文档采用markdown格式是目前最为频繁的一种格式 2.该软件生成的文档后缀名是.md结尾 3.下载网址 https://www.typora.io/ 二. ...
- Solution -「LOJ #6029」「雅礼集训 2017」市场
\(\mathcal{Description}\) Link. 维护序列 \(\lang a_n\rang\),支持 \(q\) 次如下操作: 区间加法: 区间下取整除法: 区间求最小值: 区 ...
- Solution -「APIO 2016」「洛谷 P3643」划艇
\(\mathcal{Description}\) Link & 双倍经验. 给定 \(n\) 个区间 \([a_i,b_i)\)(注意原题是闭区间,这里只为方便后文描述),求 \(\ ...
- C# 给Word每一页设置不同图片水印
Word中设置水印时,可加载图片设置为水印效果,但通常添加水印效果时,会对所有页面都设置成统一效果,如果需要对每一页或者某个页面设置不同的水印效果,则可以参考本文中的方法.下面,将以C#代码为例,对W ...
- Zabbix 6.0:原生高可用(HA)方案部署
Blog:博客园 个人 本部署文档适用于CentOS 8.X/RHEL 8.X/Anolis OS 8.X/AlmaLinux 8.X/Rockey Linux 8.X. 原生的HA方案终于来了 相比 ...
- java 获取真实IP
1.java代码 /** 获取客户端IP */ public static final String getClientIp(HttpServletRequest request) { String ...
- HTTP攻击与防范-PHP安全配置
实验目的 1.了解PHP攻击带来的危险性. 2.掌握PHP攻击的原理与方法 3.掌握防范攻击的方法 实验原理 由于网站服务器是以单一系统使用者的模式在运行,因此这个系统的使用者账号必须能够读取每个使用 ...
- Custom数据如何导入RENIX软件——网络测试仪实操
在我们日常工作中,有的时候会需要把特定的数据内容从抓包软件中导入到RENIX软件中,然后以大速率发送出去,本文描述如何进行这样的操作. 一.整理需要抓取的数据部分 1.例如有一个数据包,里面包含特定的 ...
- 【C# 程序集】把自己的程序集添加|卸载 到GAC 全局程序集缓存中
添加全局程序集缓存 Global Assembly Cache Tool (Gacutil.exe) 操作环境windows10+vs2019 cmd.exe位置在 C:\Windows\System ...
- ubuntu系统黑屏,只有一个光标
使用easyBCD删除原ubuntu引导,重建 添加新条目 引导选择900M左右那个选项