简介

JDK中的ThreadLocal可以通过get方法来获得跟当前线程绑定的值。而这些值是存储在ThreadLocal.ThreadLocalMap中的。而在ThreadLocalMap中底层的数据存储是一个Entry数组中的。

那么从ThreadLocalMap中获取数据的速度如何呢?速度有没有可以优化的空间呢?

一起来看看。

从ThreadLocalMap中获取数据

ThreadLocalMap作为一个Map,它的底层数据存储是一个Entry类型的数组:

private Entry[] table;

我们再来回顾一下ThreadLocal是怎么获取数据的:

        private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

首先根据ThreadLocal对象中的threadLocalHashCode跟table的长度进行取模运算,得到要获取的Entry在table中的位置,然后判断位置Entry的key是否和要获取的ThreadLocal对象一致。

如果一致,说明获取到了ThreadLocal绑定的对象,直接返回即可。

如果不一致,则需要再次进行查找。

我们看下再次查找的逻辑:

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

getEntryAfterMiss的逻辑是,先判断Entry中的对象是否要获取的对象,如果是则直接返回。

如果Entry中的对象为空,则触发清除过期Entry的方法。否则的话计算出下一个要判断的地址,再次进行判断,直到最终找到要找到的对象为止。

可以看到,如果第一次没有找到要找到的对象的话,后面则可能会遍历多次,从而造成执行效率变低。

那么有没有可以提升这个寻找速度的方法呢?答案是肯定的。

FastThreadLocal

之前我们提到了,Netty中的本地对象池技术,netty为其创建了一个专门的类叫做Recycler。虽然Recycler中也使用到了ThreadLocal,但是Recycler使用的threadLocal并不是JDK自带的ThreadLocal,而是FastThreadLocal。和它关联的ThreadLocalMap叫做InternalThreadLocalMap,和它关联的Thread叫做FastThreadLocalThread。netty中的类和JDK中的类的对应关系如下:

netty中的对象 JDK中的对象
FastThreadLocalThread Thread
InternalThreadLocalMap ThreadLocal.ThreadLocalMap
FastThreadLocal ThreadLocal

我们先来看FastThreadLocalThread。不管它到底快不快,既然是Thread,那么自然就要继承自JDK的Thread:

public class FastThreadLocalThread extends Thread

和Thread一样,FastThreadLocalThread中也有一个ThreadLocalMap,叫做InternalThreadLocalMap,它是FastThreadLocalThread的private属性:

private InternalThreadLocalMap threadLocalMap;

InternalThreadLocalMap中也有一个ThreadLocal对象,叫做slowThreadLocalMap,是在fastThreadLocalMap不生效的时候使用的。

接下来我们来看下这个ThreadLocalMap为什么快:

    public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}

从get方法可以看到,如果当前thread是FastThreadLocalThread的话,则会去调用fastGet方法,否则调用slowGet方法。

slowGet方法就是使用传统的ThreadLocal来get:

    private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}

我们重点关注下fastGet方法:

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}

这里fast的效果就出现了,fastGet直接返回了thread中的InternalThreadLocalMap对象,不需要进行任何查找的过程。

再看下FastThreadLocal如何使用get方法来获取具体的值:

    public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
} return initialize(threadLocalMap);
}

可以看到FastThreadLocal中的get首先调用了InternalThreadLocalMap的get方法,直接返回了FastThreadLocalThread中的InternalThreadLocalMap对象,这个速度是非常快的。

然后直接使用FastThreadLocal中的index,来获取threadLocalMap中具体存储数据的数组中的元素:

    public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}

因为是直接index访问的,所以也非常快。这就是fast的由来。

那么有同学会问题了,FastThreadLocal中的index是怎么来的呢?

    private final int index;

    public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}

而InternalThreadLocalMap中的nextVariableIndex方法是一个静态方法:

    public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}

也就是说,只要new一个FastThreadLocal,该对象中,就会生成一个唯一的index。然后FastThreadLocal使用该index去InternalThreadLocalMap中存取对象。这样就不存在ThreadLocal那种需要多次遍历查找的情况。

总结

FastThreadLocal是和FastThreadLocalThread配套使用才会真正的fast,否则的话就会fallback到ThreadLocal去执行,大家一定要注意这一点。

更多内容请参考 http://www.flydean.com/48-netty-fastthreadlocal/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

netty系列之:给ThreadLocal插上梦想的翅膀,详解FastThreadLocal的更多相关文章

  1. 时序数据库(TSDB)-为万物互联插上一双翅膀

    本文由  网易云发布. 时序数据库(TSDB)是一种特定类型的数据库,主要用来存储时序数据.随着5G技术的不断成熟,物联网技术将会使得万物互联.物联网时代之前只有手机.电脑可以联网,以后所有设备都会联 ...

  2. Linux上的free命令详解、swap机制

    Linux上的free命令详解   解释一下Linux上free命令的输出. 下面是free的运行结果,一共有4行.为了方便说明,我加上了列号.这样可以把free的输出看成一个二维数组FO(Free ...

  3. 【HANA系列】SAP HANA XS使用服务器JavaScript Libraries详解

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用服务器 ...

  4. Linux Shell系列教程之(八)Shell printf命令详解

    本文是Linux Shell系列教程的第(八)篇,更多shell教程请看:Linux Shell系列教程 在上一篇:Linux Shell系列教程之(七)Shell输出这篇文章中,已经对Shell p ...

  5. 【iOS 使用github上传代码】详解

    [iOS 使用github上传代码]详解 一.github创建新工程 二.直接添加文件 三.通过https 和 SSH 操作两种方式上传工程 3.1https 和 SSH 的区别: 3.1.1.前者可 ...

  6. SQL Server时间粒度系列----第4节季、年时间粒度详解

    本文目录列表: 1.SQL Server季时间粒度2.SQL Server年时间粒度 3.总结语 4.参考清单列表   SQL Serve季时间粒度       季时间粒度也即是季度时间粒度.一年每3 ...

  7. Uploadify 上传文件插件详解

    Uploadify 上传文件插件详解 Uploadify是JQuery的一个上传插件,实现的效果非常不错,带进度显示.不过官方提供的实例时php版本的,本文将详细介绍Uploadify在Aspnet中 ...

  8. Oracle 11g客户端在Linux系统上的配置步骤详解

    Oracle 11g客户端在Linux系统上的配置步骤详解 2011-07-26 10:47 newhappy2008 CSDN博客 字号:T | T 本文我们主要介绍了Oracle 11g客户端在L ...

  9. Python操作redis学习系列之(集合)set,redis set详解 (六)

    # -*- coding: utf-8 -*- import redis r = redis.Redis(host=") 1. Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合 ...

  10. 【HANA系列】SAP HANA XS使用JavaScript数据交互详解

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用Jav ...

随机推荐

  1. File.delete()和Files.delete(Path path)的区别

    文件删除时可以选择File.delete()和Files.delete(Path path),这两个方法到底有什么区别呢? //删除暂存的pdfFile file =new File(pdfFilen ...

  2. IDEA git分支回退指定的历史版本

    https://blog.csdn.net/woshi1226a/article/details/86664159

  3. flutter打包android的一些配置修改(解决白屏,视频闪退)

    1.打包后视频播放闪退 视频播放器选择了flutter_tencentplayer(https://github.com/qq326646683/flutter_tencentplayer) 解决:不 ...

  4. 图书管理系统---基于form组件和modelform改造添加和编辑

    添加 基于form组件改造 步骤1 1.为了区分自己写的form类和视图逻辑,所以工作中需要区分开来,那么就可以在应用下创建一个叫utils的文件夹,专门存放我们写的form类,py文件名随便起 2. ...

  5. Android Compose开发

    目录 好处 入门 Composable 布局 其他组件 列表 verticalScroll 延迟列表 内容内边距 性能 修饰符 偏移量 requiredSize 滚动 添加间距Spacer Butto ...

  6. 【LeetCode回溯算法#02】组合总和III

    组合总和III 力扣题目链接(opens new window) 找出所有相加之和为 n 的 k 个数的组合.组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字. 说明: 所有数字 ...

  7. PHP项目&变量覆盖&反序列化&未授权访问&身份验证

    CNVD拿1day-验证&未授权-xhcms&Bosscms 此种漏洞由于没有什么关键函数,所以需要通过功能点去进行测试. Bosscms未授权访问 CNVD官网上搜索Bosscms未 ...

  8. FolkMQ 作个简单的消息中间件(最简单的那种), v1.3.1 发布

    功能简介 角色 功能 生产端(或发起端) 发布消息.定时消息(或叫延时).顺序消息.可过期消息.事务消息.发送消息(rpc)支持 Qos0.Qos1 消费端(或接收端) 订阅.取消订阅.消费-ACK( ...

  9. 案例7:将"picK"译成密码

    密码规则:用当前字母后面的第五各字符来代替当前字符.比如字符'a'后面的第5个字符为'f', 则使用'f'代替'a'.编写程序,实现该功能. 示例代码如下: #define _CRT_SECURE_N ...

  10. 正则表达式 <h2>kk</h2> 替换为 <h2 id="kk">kk</h2>

    `<h2>kk</h2><h2>k333k</h2>`.replace(/\<h2>(.*?)<\/h2>/g, `<h2 ...