Netty之DefaultAttributeMap与AttributeKey的机制和原理

news2024/11/19 4:26:41

  为什么要分析DefaultAttributeMap和AttributeKey呢?我自己对Netty也是一个不断的学习过程,从前面几篇Netty分析的博客中,可以看出,Netty是比较博大精深的,很像java.util.concurrent.*包中的源码,如果只是看主体流程,就会觉得Netty不就是一个接收数据,并将数据封装到业务端代码,业务端处理完代码后,再将数据封装返回给客户端不? 主体代码固然不错,但这些细节代码也值得我们学习,正是因为这些细节处理得足够好,我觉得Netty的高性能,离不开这些细节代码的支持。因此今天要分析一下DefaultAttributeMap及AttributeKey的实现原理。

   在分析Netty 服务端源码中遇到一个代码块,请看下图。
在这里插入图片描述
在这里插入图片描述
  在ServerBootstrap的bind()方法调用过程中,调用了init()方法 。上图中的Channel就是NioServerSocketChannel,那NioServerSocketChannel和DefaultAttributeMap有什么关系呢?请看下图
在这里插入图片描述
  弄明白它们之间的关系,接下来进入DefaultAttributeMap和AttributeKey的原理分析 。
  既然要分析源码,那肯定还是从例子学起。 请看下面例子。

public static void main(String[] args) {
    AttributeKey<String> key = AttributeKey.newInstance("zhangsan");
    DefaultAttributeMap defaultAttributeMap = new DefaultAttributeMap();
    Attribute attr= defaultAttributeMap.attr(key);
    attr.set(10);

    System.out.println(attr.getAndRemove());
    System.out.println(attr.get());
    AttributeKey<String> key1 = AttributeKey.newInstance("zhangsan");
}

  创建一个AttributeKey对象,并为其设置值为10 , 调用getAndRemove()方法获取值为10,再调用get()方法返回空, 显然,10在getAndRemove()方法中被移除再,再次实例化name为zhangsan的AttributeKey,抛出异常。
在这里插入图片描述
  我们就围绕着上述例子来分析源码 。

AttributeKey

  首先看AttributeKey的类结构

在这里插入图片描述

public final class AttributeKey<T> extends AbstractConstant<AttributeKey<T>> {

    private static final ConstantPool<AttributeKey<Object>> pool = new ConstantPool<AttributeKey<Object>>() {
        @Override
        protected AttributeKey<Object> newConstant(int id, String name) {
            return new AttributeKey<Object>(id, name);
        }
    };

    public static <T> AttributeKey<T> valueOf(String name) {
        return (AttributeKey<T>) pool.valueOf(name);
    }


    public static boolean exists(String name) {
        return pool.exists(name);
    }

    public static <T> AttributeKey<T> newInstance(String name) {
        return (AttributeKey<T>) pool.newInstance(name);
    }

    public static <T> AttributeKey<T> valueOf(Class<?> firstNameComponent, String secondNameComponent) {
        return (AttributeKey<T>) pool.valueOf(firstNameComponent, secondNameComponent);
    }

    private AttributeKey(int id, String name) {
        super(id, name);
    }
}

  当 newInstance( name) 方法时,实际上调用的是pool.newInstance(name)方法 ,进入pool的newInstance()方法 。

private final ConcurrentMap<String, T> constants = PlatformDependent.newConcurrentHashMap();

private final AtomicInteger nextId = new AtomicInteger(1);

public T newInstance(String name) {
    checkNotNullAndNotEmpty(name);
    return createOrThrow(name);
}

private T createOrThrow(String name) {
    T constant = constants.get(name);
    if (constant == null) {
        final T tempConstant = newConstant(nextId(), name);
        // 如果name已经存在于constants中,则不会执行插入操作 
        constant = constants.putIfAbsent(name, tempConstant);
        if (constant == null) {
            return tempConstant;
        }
    }
    
    throw new IllegalArgumentException(String.format("'%s' is already in use", name));
}

public final int nextId() {
    return nextId.getAndIncrement();
}

  从上面代码中得知,最终调用的是AttributeKey中的newConstant()方法,创建了AttributeKey()对象,当然id为自增id , name 为我们传入的名字,在例子中两次调用AttributeKey.newInstance(“zhangsan”)方法, 第二次抛出IllegalArgumentException异常。
在这里插入图片描述

  原因就在于createOrThrow()方法 , 如果constants中有name相同的key时, 则无法进行插入操作。引发后面的IllegalArgumentException异常。

  接下来看DefaultAttributeMap的实现。

DefaultAttributeMap
public class DefaultAttributeMap implements AttributeMap {

	// 以原子方式更新attributes变量的引用
    private static final AtomicReferenceFieldUpdater<DefaultAttributeMap, AtomicReferenceArray> updater =
            AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, AtomicReferenceArray.class, "attributes");
	// 默认桶的大小4  
    private static final int BUCKET_SIZE = 4;
    // 掩码为3 
    private static final int MASK = BUCKET_SIZE  - 1;

    // Initialize lazily to reduce memory consumption; updated by AtomicReferenceFieldUpdater above.
    // 延迟初始化,节约内存
    private volatile AtomicReferenceArray<DefaultAttribute<?>> attributes;


    @Override
    public <T> Attribute<T> attr(AttributeKey<T> key) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
        // 当attributes为空时则创建它,默认数组长度4
        if (attributes == null) {
            // Not using ConcurrentHashMap due to high memory consumption.
            // 没有使用ConcurrentHashMap为了节约内存
            attributes = new AtomicReferenceArray<DefaultAttribute<?>>(BUCKET_SIZE);
			
			// 原子方式更新attributes,如果attributes为null则把新创建的对象赋值,原子方式更新解决了并发赋值问题
            if (!updater.compareAndSet(this, null, attributes)) {
                attributes = this.attributes;
            }
        }

		// 计算key所在数组下标 ,相当于 key.id % 3   
        int i = index(key);
        DefaultAttribute<?> head = attributes.get(i);
        
        // 头部为空说明第一次添加,这个方法可能多个线程同时调用,因为判断head全部为null
        if (head == null) {
            // No head exists yet which means we may be able to add the attribute without synchronization and just
            // use compare and set. At worst we need to fallback to synchronization and waste two allocations.
            // 创建一个空对象为链表结构的起点
            head = new DefaultAttribute();
            // 创建一个attr对象,把head传进去 
            DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
            // 链表头head的next = attr
            head.next = attr;
            // attr的prev = head
            attr.prev = head;
            // 给数组i位置赋值,这里用compareAndSet原子更新方法解决并发问题,只有i位置为null能够设置成功,且只有一个线程能设置成功
            if (attributes.compareAndSet(i, null, head)) {
                // we were able to add it so return the attr right away
                return attr;
            } else {
            	//设置失败的,说明数组i位置已经被其它线程赋值
            	//所以要把head重新赋值,不能用上面new出来的head,需要拿到之前线程设置进去的head
                head = attributes.get(i);
            }
        }
        
        // 对head同步加锁
        synchronized (head) {
        	//curr 赋值为head    head为链表结构的第一个变量
            DefaultAttribute<?> curr = head;
            for (;;) {
            	// 依次获取next 
                DefaultAttribute<?> next = curr.next;
                // next==null,说明curr为最后一个元素 , 
                // 将新创建的节点插入到尾节点 
                if (next == null) {
                	// 创建一个新对象传入head和key
                    DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
                    // curr后面指向attr
                    curr.next = attr;
                    // attr的前面指向curr
                    attr.prev = curr;
                    // 返回新对象
                    return attr;
                }

				// 如果next的key等于传入的key,并且没有被移除
				// 则返回链表中的节点即可
                if (next.key == key && !next.removed) {
                	// 这直接返回next
                    return (Attribute<T>) next;
                }
                // 否则把curr变量指向下一个元素
                curr = next;
            }
        }
    }
    private static int index(AttributeKey<?> key) {
    	// 与掩码&运算,数值肯定<=mask 正好是数组下标
        return key.id() & MASK;
    }

  首先要理清DefaultAttributeMap的结构,其实是一个数组长度为4的数组链表结构 。

在这里插入图片描述

  上面代码分3种情况 。 我们用图来展示 。

  1. 第一种情况, key 对应的数组下标处没有头节点。创建头节点,并将创建next节点,再将头节点CAS操作插入到数组对应下标处。
    在这里插入图片描述

  2. 多线程并发将头节点插入到数组索引为3处,此时必然一个线程成功,一个线程失败,失败的线程继续将新创建的next节点插入到index为3的链表结尾 。
    在这里插入图片描述

  3. 如果next1的key和传入的key相等,并且next1 并没有被移除掉,则返回next1即可。
    在这里插入图片描述
      hasAttr()方法的实现原理和attr()方法的实现原理类似,这里不做过多分析 。

    @Override
    public <T> boolean hasAttr(AttributeKey<T> key) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        // attributes为null直接返回false
        AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
        if (attributes == null) {
            // no attribute exists
            return false;
        }
		// 计算数组下标
        int i = index(key);
        // 获取头 为空直接返回false
        DefaultAttribute<?> head = attributes.get(i);
        if (head == null) {
            // No attribute exists which point to the bucket in which the head should be located
            return false;
        }

        // We need to synchronize on the head.
       // 对head同步加锁
        synchronized (head) {
            // Start with head.next as the head itself does not store an attribute.
            // 从head的下一个开始,因为head不存储元素
            DefaultAttribute<?> curr = head.next;
            // 为null说明没有节点了
            while (curr != null) {
            	// key一致并且没有被移除则返回true
                if (curr.key == key && !curr.removed) {
                    return true;
                }
                // curr指向下一个
                curr = curr.next;
            }
            return false;
        }
    }

  从DefaultAttribute的结构得知,其实在之前的图表中,每个节点还有一条线没有画, 如果画上去太多了,不方便突出重点 。 这里补充上,实际DefaultAttribute节点之间的关系图如下 。

在这里插入图片描述

    @SuppressWarnings("serial")
    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;
        }

        // Special constructor for the head of the linked-list.
        DefaultAttribute() {
            head = this;
            key = null;
        }

        @Override
        public AttributeKey<T> key() {
            return key;
        }

        @Override
        public T setIfAbsent(T value) {
            while (!compareAndSet(null, value)) {
                T old = get();
                if (old != null) {
                    return old;
                }
            }
            return null;
        }

        @Override
        public T getAndRemove() {
            removed = true;
            T oldValue = getAndSet(null);
            remove0();
            return oldValue;
        }

  从DefaultAttribute的代码中得知, 每一个节点DefaultAttribute的removed属性是volatile修饰的。 接着看remove()方法的实现。

        @Override
        public void remove() {
            removed = true;
            set(null);
            remove0();
        }

        private void remove0() {
            synchronized (head) {
                if (prev == null) {
                    // Removed before.
                    return;
                }

                prev.next = next;

                if (next != null) {
                    next.prev = prev;
                }

                // Null out prev and next - this will guard against multiple remove0() calls which may corrupt
                // the linked list for the bucket.
                prev = null;
                next = null;
            }
        }
    }
}

  remove()方法的第一步就是将removed设置为true,其他线程立即可见,如果此时正存在插入查询操作,则此时会将key插入到队列尾部。 我们来模拟key=zhangsan 的节点并发执行移除再添加的操作。
在这里插入图片描述

  1. 线程1执行下面加粗代码。
public void remove() {
        removed = true;
        set(null);
        remove0();
    }

    private void remove0() {
        synchronized (head) {
            if (prev == null) {
                return;
            }

            prev.next = next;

            if (next != null) {
                next.prev = prev;
            }
            
            prev = null;
            next = null;
        }
    }
}
  1. 线程 2 抢到同步锁,并执行下面红框代码
    在这里插入图片描述

  2. 因为线程2 抢到锁, 因此继续执行,将key=zhangsan构建出DefaultAttribute节点,插入到队列尾部。 继续执行下面红框中的代码 。
    在这里插入图片描述

  3. 线程 1 等待线程 2 释放锁后,抢到同步锁。 执行后续的节点移除操作
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/191772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微电网】微电网的分布式电源优化配置研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

企业工程管理系统源码-专注项目数字化管理

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保项…

关于卷积过程中通道数如何变化问题

以RGB图像为例。 一个12*12的像素图&#xff0c;对其进行5*5的卷积&#xff0c;最后得到一个8*8【计算过程&#xff1a;(12-5)/118】的像素图。 RGB图像有3个通道&#xff08;12*12*3&#xff09;&#xff0c;所以卷积核也要有3个通道&#xff08;5*5*3&#xff09;&am…

第一章 隐私计算科普与解读

前言 提醒&#xff1a;全文10千字&#xff0c;预计阅读时长15分钟&#xff1b;读者&#xff1a;对隐私计算感兴趣的小伙伴&#xff1b;目的&#xff1a;读者利用15~30 分钟对本文沉浸式阅读理解&#xff0c;能够掌握隐私计算 80% 的概念&#xff1b;关键词 &#xff1a;隐私计算…

好用的搜索工具listary

发现一个好工具记录一下。以后好好用起来。 这个工具我安装后&#xff0c;跟着引导教程&#xff0c;学做了一些操作立马就爱上了。 Listary&#xff1a;大幅度提高本地文件浏览与搜索速度效率的「超级神器」 百度安全验证https://baijiahao.baidu.com/s?id17127561426219890…

【Python】基于you-get下载网页视频

文章目录1 前言2 you-get2.1 安装2.2 简单使用2.3 扩展3 下载网页视频3.1 概述3.2 下载网页4 代码1 前言 过年了&#xff0c;想给家里长辈下几首戏曲&#xff0c;于是找到一个发布戏曲的网站&#xff0c;虽然可以通过IDM插件的资源嗅探来一一下载&#xff0c;但是内容太多&…

【JavaScript】数据劫持详解

&#x1f4bb; 【JavaScript】数据劫持 &#x1f3e0;专栏&#xff1a;JavaScript &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向&#xff1a;目前主攻前端&#…

这就是传说中超难的N皇后?——详细图解!

✔️本文主题&#xff1a;回溯算法之N皇后 算法 ✔️题目链接&#xff1a;N皇后 详解N皇后一、前言二、题目信息三、解题思路四、参考代码五、结语一、前言 大家好久不见&#xff0c;今天我们一起来学习一道很经典、也很有难度的一道题目——N皇后 二、题目信息 按照国际象棋…

Spring-基础知识二

Spring9.Spring JdbcTemplate的使用9.1 JdbcTemplate入门9.1.1 需要的包9.1.2 代码测试9.2 将数据源和jdbcTemplate交给Spring来管理9.2.1 druid连接池9.2.2 使用外部文件配置数据连接信息9.3 基于JdbcTemplate实现DAO9.Spring的事务管理机制9.1 PlatformTransactionManager 事…

第十章 面向对象编程(高级)

一、类变量和类方法&#xff08;P374&#xff09; 1. 类变量 定义语法&#xff1a; 访问修饰符 static 数据类型 变量名&#xff1b; 类变量也叫静态变量/静态属性&#xff0c;是该类的所有对象共享的变量&#xff0c;任何一个该类的对象去访问它时&…

微服务 热点流控 规则-授权 系统规则 自定义返回

微服务 热点流控 规则-授权 系统规则 自定义返回Sentinel-热点流控操作示例Sentinel规则-授权操作示例Sentinel规则-系统规则Sentinel自定义异常返回Sentinel-热点流控 拿商品举例&#xff0c;当一个商品的查询请求量异常火爆的时候&#xff0c;应该对该商品的查询请求进行限流…

FineReport使用

目录报表命名规范数据集命名规则参数命名规则条件属性命名规则超链接命名规范决策报表组件命名规则普通报表悬浮元素命名规则用户权限模版版本管理FineDB内置数据库外置数据库配置外接数据库新建数据库外接数据库配置入口配置外接数据库数据表权限控制&#xff1a;用户-部门职位…

【JavaEE】锁策略 + synchronized原理 + CAS + JUC下常用类和接口 + 死锁

目录 锁策略 乐观锁VS悲观锁 轻量级锁VS重量级锁 自旋锁VS挂起等待锁 互斥锁VS读写锁 公平锁VS非公平锁 可重入锁VS不可重入锁 synchronized原理 synchronized特性 synchronized优化机制 加锁过程优化 锁消除 锁粗化 CAS CAS概念 CAS原理 CAS应用 自旋锁的实…

Vite+Vue3+TypeScript 搭建开发脚手架

Vite前端开发与构建工具 开发环境中&#xff0c;vite无需打包&#xff0c;可快速的冷启动 真正的按需编译&#xff0c;不需要等待整个应用编译完成 一个开发服务器&#xff0c;它基于原生ES模块 提供了丰富的内建功能&#xff0c;速度快模块热更新&#xff08;HMR&#xff0…

2022年10个最流行Blender插件

如果你从事平面设计、动画或 3D 建模&#xff0c;您可能听说过Blender&#xff0c;这是一款开源的一体化 3D 图形软件。Blender 配备了适用于各种领域的工具和功能&#xff0c;包括 3D 动画、计算机辅助设计、纹理编辑、特殊效果等。 Blender 的最新版本3.0于 2021 年 12 月上…

LeetCode题目笔记——1566. 重复至少 K 次且长度为 M 的模式

文章目录题目描述题目难度——简单方法一&#xff1a;模拟代码/C总结题目描述 给你一个正整数数组 arr&#xff0c;请你找出一个长度为 m 且在数组中至少重复 k 次的模式。 模式 是由一个或多个值组成的子数组&#xff08;连续的子序列&#xff09;&#xff0c;连续 重复多次…

【面试】vue组件style中scoped的作用是什么?什么是scoped穿透?

vue组件style中scoped的作用是什么&#xff1f; 在Vue文件中的style标签上有一个特殊的属性——scoped。scoped属性是 HTML5 中的新属性&#xff0c;是一个布尔属性&#xff0c;如果使用该属性&#xff0c;则css样式仅仅只能应用到当前的Vue组件&#xff0c;避免组件之间样式相…

EfficientNet v1 v2

EfficientNet v1 增加网络的深度depth能够得到更加丰富、复杂的特征并且能够很好的应用到其它任务中。但网络的深度过深会面临梯度消失&#xff0c;训练困难的问题。增加网络的width能够获得更高细粒度的特征并且也更容易训练&#xff0c;但对于width很大而深度较浅的网络往往很…

Base64编码

介绍 Base64 编码 Base64 是一种使用 64 个可打印字符来表示二进制数据的编码方式。 Base64 中的 64 个可打印字符包括&#xff1a;大小写字母 a - z、阿拉伯数字 0 - 9&#xff0c;这样共有 62 个字符&#xff0c;另外两个可打印字符在不同的系统中而不同。RFC 4648 标准中&…

程序员可以不看书,但不可以不知道这些网站!

程序员可以不看书&#xff0c;但是不能停止学习。如果你不喜欢看书&#xff0c;这些网站可以先收藏下来&#xff01; 花了两天整理出的程序员常看的网站&#xff0c;纯纯干货来了↓↓ 一、学习网站 ①菜鸟教程 这个网站有HTML、CSS、Javascript、PHP、C、Python等各种基础编…