Netty之DefaultAttributeMap与AttributeKey的机制和原理

知是行的主意,行是知的功夫。这篇文章主要讲述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的字段就没有详细画出来
Netty之DefaultAttributeMap与AttributeKey的机制和原理

文章图片

数组默认创建大小为4,如下图所示
Netty之DefaultAttributeMap与AttributeKey的机制和原理

文章图片

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值存入,方法是为空才放入的意思,每次都会返回一个初始化idkey值的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 原理newInstancevalueOf 的 原理 异常类似,都是乐观锁的思想,只是 在多线程环境下前者要 抛出 异常(不太准确,后面总结会纠正),后者直接返回同一个
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:如果 namenull、空字符串时抛出异常,不存在就创建一个,且多线程随先创建返回谁。
newInstance : 如果namenull、空字符串或存在时,就抛出异常,且多线程创建,第一个成功创建后,其他能判断到第一个if里面的的几个线程返回创建值,其他线程抛出异常。
借鉴:
简书:https://www.jianshu.com/p/e7d9a2e8c0ac
官方文档:https://netty.io/4.1/api/index.html
三、结束语评论区可留言,可私信,可互相交流学习,共同进步,欢迎各位给出意见或评价,本人致力于做到优质文章,希望能有幸拜读各位的建议!
个人博客园:https://www.cnblogs.com/fyphome

    推荐阅读