当 Netty 用作长连接的时候,比如websocket
的时候,如果有一个类似ThreadLocal
的功能,能够将相关参数绑定到这个Channel
上的时候,是不是会省下很多功夫?
最初遇到类似的问题,我是自己在那里维护一个Channel
的id
和对应值的一个Map
,然后在这个通道关闭的时候,移除相关映射,这种办法肯定是可以的,不过该方法需要自己维护 Map,并监听通道关闭事件,不够简单和方便,经过学习这里介绍一种Netty
原生自带的且更加简单的方法。
//1、定义一个 属性 Key,其中 String 表示该 key 映射的是什么类型的值
val ATTR_KEY: AttributeKey<String> = AttributeKey.valueOf("mt-user-info")
//2.赋值操作
ctx.channel().attr(ATTR_KEY).set("ok")
//3.取值操作
val msg = ctx.channel().attr(ATTR_KEY).get()
核心代码就这 3 句,这样就可以随时随地的从channel
上线文获取到对应的值,这个和ThreadLocal
有点类似吧。
同时当channel
关闭的时候,netty
会自动帮我们销毁对应的映射,我们不需要担心这点。
最后我们来简单看看netty
对于的实现,我本来以为它内部维护的是一个Map
,这个想法其实不够正确,我们看下面的代码:
private volatile AtomicReferenceArray<DefaultAttribute<?>> attributes;
public <T> Attribute<T> attr(AttributeKey<T> key) {
ObjectUtil.checkNotNull(key, "key");
AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
if (attributes == null) {
// Not using ConcurrentHashMap due to high memory consumption.
attributes = new AtomicReferenceArray<DefaultAttribute<?>>(BUCKET_SIZE);
if (!updater.compareAndSet(this, null, attributes)) {
attributes = this.attributes;
}
}
int i = index(key);
DefaultAttribute<?> head = attributes.get(i);
//为空的时候,表示要添加头
if (head == null) {
head = new DefaultAttribute();
DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
head.next = attr;
attr.prev = head;
if (attributes.compareAndSet(i, null, head)) {
// we were able to add it so return the attr right away
return attr;
} else {
head = attributes.get(i);
}
}
//不为空的时候,构建链表结构
synchronized (head) {
DefaultAttribute<?> curr = head;
for (;;) {
DefaultAttribute<?> next = curr.next;
if (next == null) {
DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
curr.next = attr;
attr.prev = curr;
return attr;
}
if (next.key == key && !next.removed) {
return (Attribute<T>) next;
}
curr = next;
}
}
}
attributes
这个类型是AtomicReferenceArray
,你可以简单理解为这个是一个并发安全的数组
DefaultAttribute
是什么样子的了? private static final class DefaultAttribute<T> extends AtomicReference<T> implements Attribute<T> {
private static final long serialVersionUID = -2661411462200283011L;
// The head of the linked-list this attribute belongs to
private final DefaultAttribute<?> head;
private final AttributeKey<T> key;
// Double-linked list to prev and next node to allow fast removal
private DefaultAttribute<?> prev;
private DefaultAttribute<?> next;
// Will be set to true one the attribute is removed via getAndRemove() or remove()
private volatile boolean removed;
DefaultAttribute(DefaultAttribute<?> head, AttributeKey<T> key) {
this.head = head;
this.key = key;
}
}
prev
和next
这不是就是链表
!所以说本质来讲,netty
内部也是维护了自己的一套数组和链表的结构,这个和map
本质上是类似的。
不同之处在于netty
可能觉得你的参数不会特别多,所以没有搞链表过长的时候,转为红黑树这样的复杂结构。
至于为什么不用 本来平台就有的Map
了,我个人觉得可能是netty
为了不依赖某个版本的 JDK
,毕竟JDK7
和JDK8
的Map
内部实现上是有差异的!