上节讲过了ThreadLocal的源码,这一节我们来看下FastThreadLocal。这个我觉得要比ThreadLocal要简单,因为缺少了对于Entry的清理和整理工作,所以ThreadLocal的效率更高。

跟ThreadLocal一样,我们也先给一个结构图:

大家看这个图跟ThreadLocal有哪些区别,首先是用一个Object数组来替代了Entry数组,不再是key键值对的形式。 另外Object[0]存储一个Set<FastThreadLocal<?>>集合。

OK,看完这个,源码就很好理解了,我们还是先看下 InternalThreadLocalMap 构造函数

     private InternalThreadLocalMap() {
super(newIndexedVariableTable());
} private static Object[] newIndexedVariableTable() {
Object[] array = new Object[32]; //初始化 32 长度的Object数组
Arrays.fill(array, UNSET); // 将每个元素初始化成UNSET 这里的UNSET 可以理解为占位符, 因为null会被认为成有效值
return array;
}
10    UnpaddedInternalThreadLocalMap(Object[] indexedVariables) {
11 this.indexedVariables = indexedVariables; // 将创建的array赋给 Object[] indexedVariables;
12 }

看完这个,我们来看下数组的索引值是什么设置的,打开  FastThreadLocal, 看下全局变量。

 private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

上面这个了解类的加载机制的应该很清楚 variablesToRemoveIndex  初始化的时机。想要了解类的加载过程的 请移步类的加载机制(一)

     static final AtomicInteger nextIndex = new AtomicInteger(); // 原子类

     public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement(); // 先获取值,在增加,所以index = 0
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}

由此得出,variablesToRemoveIndex  = 0 固定值。再看下 FastThreadLocal 的构造方法

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

大家看到这个,是不是就想到了,也就是说每个 FastThreadLocal 都有一个唯一的 index 值 , 那么跟ThreadLocal相比的话,ThreadLocal要 先获取一个hash值,然后再根据Entry数组的长度进行运算得到一个索引值,所以说这样也是Netty的这个FastThreadLocal效率更高的原因之一。

为了更好地讲下面的内容,我们再看一个 FastThreadLocalThread

继承了Thread,增加了成员变量 InternalThreadLocalMap threadLocalMap.  不再是放在Thread中的成员变量了,看到这个想到了什么? 那么也就是说获取某个线程存储Object数组结构的Map,是从FastThreadLocalThread中获取。

好了介绍完上面,看set方法

     public final void set(V value) {
if (value != InternalThreadLocalMap.UNSET) { // 如果不是UNSET
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 获取map
if (setKnownNotUnset(threadLocalMap, value)) { // 新增或者更新值
registerCleaner(threadLocalMap); // 如果返回true, 代表新增, 设置清理位
}
} else {
remove(); // 如果入参是一个UNSET,那么执行删除逻辑
}
}

看下 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();

     public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) { // 如果当前线程是一个FastThreadLocalThread
return fastGet((FastThreadLocalThread) thread); // 执行fastGet 这个名字是很有意思。。。。
} else {
return slowGet(); // 要不然就slowGet() Netty叫ThreadLocal Slow。。。
}
}
     private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { // 如果当前的map没有设置,则新创建一个
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); // 这里就是获取了 FastThreadLocalThread 中的成员变量 threadLocalMap
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}

看下新增或者更新的逻辑

     private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
if (threadLocalMap.setIndexedVariable(index, value)) { // index 是当前FastThreadLocal的索引 ,如果是 新增,那么这个方法返回true ,如果是修改则返回 false
addToVariablesToRemove(threadLocalMap, this); // 新增的话,需要将当前的这个FastThreadLocal添加到Set<FashThreadLocal>集合中
return true; // 新增返回trye
}
return false; // 更新返回false
}
     public boolean setIndexedVariable(int index, Object value) {
Object[] lookup = indexedVariables; // 当前的Object[]对象
if (index < lookup.length) { // 检查当前的索引是否超过了Object数组的长度
Object oldValue = lookup[index]; // 获取当前的值
lookup[index] = value; // 将新值设置进去
return oldValue == UNSET; // 判断之前的值是不是占位符,如果是则代表新增,不是代表更新
} else {
expandIndexedVariableTableAndSet(index, value); // 如果超过了,则需要进行扩容
return true;
}
}
     private void expandIndexedVariableTableAndSet(int index, Object value) { // 扩容逻辑
Object[] oldArray = indexedVariables; // 当前的Object数组
final int oldCapacity = oldArray.length; // 当前的长度
int newCapacity = index; // index是这个FastThreadLocal对应的索引值
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++; // 这段其实也很有意思,其实是为了计算新的数组的容量,会变成下一个档位的2的次方大小,比如 1->2 2->4 3->4 4->8 5->8 6->8 7->8 8->16 18->32, 如果不理解,
// 可以自己写一个main方法试一下,后面我们在讲Netty内存模型的时候大家也会看到这么一段。 Object[] newArray = Arrays.copyOf(oldArray, newCapacity); // 将老的数据往新的数组进行拷贝
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET); // 将新的多出来的部分,设置占位符
newArray[index] = value; // 将index对应的值设置完
indexedVariables = newArray; // 将新的数组提升成Map中的indexedVariables
}

接下来看下set中的这句 addToVariablesToRemove(threadLocalMap, this);  根据上面的结构,新增了value,那么就需要修改set集合

     private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); // 根据上面的分析 variablesToRemoveIndex = 0 获取第0个位置的Object 然后看是不是null或者占位符
Set<FastThreadLocal<?>> variablesToRemove;
if (v == InternalThreadLocalMap.UNSET || v == null) { // 如果是占位符
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove); // 创建一个set集合放到第0的位置上
} else {
variablesToRemove = (Set<FastThreadLocal<?>>) v; // 已经有了,则直接取过来
} variablesToRemove.add(variable); // 新增新的FastThreadLocal到set集合
}

接下来看get方法

     public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 获取map
Object v = threadLocalMap.indexedVariable(index); // 根据当前的Thread的索引,获取Object
if (v != InternalThreadLocalMap.UNSET) { // 不是占位符直接返回
return (V) v;
} V value = initialize(threadLocalMap); // 如果是,则调用初始化方法
registerCleaner(threadLocalMap);
return value;
}
     private V initialize(InternalThreadLocalMap threadLocalMap) {
V v = null;
try {
v = initialValue(); // 空方法,供子类调用
} catch (Exception e) {
PlatformDependent.throwException(e);
} threadLocalMap.setIndexedVariable(index, v); // 设置初始化的值
addToVariablesToRemove(threadLocalMap, this); // 添加set集合
return v;
}

接下来看remove方法

     public final void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
} Object v = threadLocalMap.removeIndexedVariable(index); // 根据当前的Thread的索引,获取Object
removeFromVariablesToRemove(threadLocalMap, this); // 移除set集合中的元素,这里就不展开说了,上面懂了就很简单了 if (v != InternalThreadLocalMap.UNSET) {
try {
onRemoval((V) v); // 空方法,供子类调用
} catch (Exception e) {
PlatformDependent.throwException(e);
}
}
}

好了,这就算是讲完了,当然FastThreadLocal 不会整理数据和清除过期数据,是怎么防止内存泄露的呢?

看下 FastThreadLocalRunnable

 public class FastThreadLocalRunnable implements Runnable {
private Runnable runnable; public FastThreadLocalRunnable(Runnable runnable) {
this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
} public static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
} @Override
public void run() {
try {
// 运行任务
this.runnable.run();
} finally {
/**
* 线程池中的线程由于会被复用,所以线程池中的每一条线程在执行task结束后,要清理掉其InternalThreadLocalMap和其内的FastThreadLocal信息,
* 否则,当这条线程在下一次被复用的时候,其ThreadLocalMap信息还存储着上一次被使用时的信息;
* 另外,假设这条线程不再被使用,但是这个线程有可能不会被销毁(与线程池的类型和配置相关),那么其上的ThreadLocal将发生资源泄露。
*/
FastThreadLocal.removeAll();
}
}
}

使用该类将一个普通的Runnable对象进行wrap装饰,之后在调用FastThreadLocalRunnable.run()的时候,实际上会调用真实对象(即普通的Runnable对象)的run(),执行完成之后,会进行对当前线程的全量回收操作(删除当前线程上的InternalThreadLocalMap中的每一个value以及threadLocalMap本身),这样就可以有效的在线程池中复用当前线程而不必关心ftl的错乱和泄漏问题。

Netty源码分析-- FastThreadLocal分析(十)的更多相关文章

  1. Netty源码解析 -- FastThreadLocal与HashedWheelTimer

    Netty源码分析系列文章已接近尾声,本文再来分析Netty中两个常见组件:FastThreadLoca与HashedWheelTimer. 源码分析基于Netty 4.1.52 FastThread ...

  2. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第1节: FastThreadLocal的使用和创建

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 概述: FastThreadLocal我们在剖析堆外内存分配的时候简单介绍过, 它类似于JDK的ThreadL ...

  3. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第2节: FastThreadLocal的set方法

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第二节: FastThreadLocal的set方法 上一小节我们学习了FastThreadLocal的创建和 ...

  4. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第3节: recycler的使用和创建

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第三节: recycler的使用和创建   这一小节开始学习recycler相关的知识, recycler是n ...

  5. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第4节: recycler中获取对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第四节: recycler中获取对象 这一小节剖析如何从对象回收站中获取对象: 我们回顾上一小节demo的ma ...

  6. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第5节: 同线程回收对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第五节: 同线程回收对象 上一小节剖析了从recycler中获取一个对象, 这一小节分析在创建和回收是同线程的 ...

  7. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第6节: 异线程回收对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第六节: 异线程回收对象 异线程回收对象, 就是创建对象和回收对象不在同一条线程的情况下, 对象回收的逻辑 我 ...

  8. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第7节: 获取异线程释放的对象

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第七节: 获取异线程释放的对象 上一小节分析了异线程回收对象, 原理是通过与stack关联的WeakOrder ...

  9. netty源码分析(十八)Netty底层架构系统总结与应用实践

    一个EventLoopGroup当中会包含一个或多个EventLoop. 一个EventLoop在它的整个生命周期当中都只会与唯一一个Thread进行绑定. 所有由EventLoop所处理的各种I/O ...

  10. Netty源码分析(前言, 概述及目录)

    Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...

随机推荐

  1. PATA 1036. Boys vs Girls (25)

    https://www.patest.cn/contests/pat-a-practise/1036 #include <bits/stdc++.h> using namespace st ...

  2. Windows使用Python虚拟环境

    Windows使用virtualenv和virtualenvwrapper-win 在Windows上使用virtualenv进行多版本Python隔离. 安装Python 在Python官网下载Py ...

  3. Excel导出打印失败报错 (eg HSSF instead of XSSF)

    错误信息: java.lang.RuntimeException: org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: ...

  4. feign服务端出异常客户端处理的方法

    在使用feign进行远程方法调用时,如果远程服务端方法出现异常,客户端有时需要捕获,并且把异常信息返回给前端,而如果在开启熔断之后,这个异常会被消化,所以说,如果希望拿到服务端异常,feign.hys ...

  5. 花5分钟时间来了解一下高性能网关Kong会有意外收获

    前言 前几天开源发布了 Kong.Net 项目,收到了大量园友的反馈,开源当天就突破了 100 个star ,可喜可贺,但是从侧面也说明,我们 .NetCore 阵营真的非常需要拥抱开源,应该敞开心扉 ...

  6. Don’t Repeat Yourself

    The Don’t Repeat Yourself (DRY) principle states that duplication in logic should be eliminated via ...

  7. 如何正确使用Profibus插头以及终端电阻

    插头与终端电阻在Profibus通讯中有着非常重要的作用,它们使用起来非常简单,没有很多复杂的设置:但是正是由于使用简单,使得很多工程师在使用当中忽略了一些细节,导致很多通讯问题. 1 Profibu ...

  8. c++学习书籍推荐《Beyond the C++ Standard Library》下载

    百度云及其他网盘下载地址:点我 作者简介 Björn Karlsson works as a Senior Software Engineer at ReadSoft, where he spends ...

  9. JAVA十大经典排序算法最强总结(含JAVA代码实现)

    十大经典排序算法最强总结(含JAVA代码实现)   最近几天在研究排序算法,看了很多博客,发现网上有的文章中对排序算法解释的并不是很透彻,而且有很多代码都是错误的,例如有的文章中在“桶排序”算法中对每 ...

  10. [POI2008]枪战Maf题解

    问题 C: [POI2008]枪战Maf 时间限制: 1 Sec  内存限制: 256 MB 题目描述 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺 ...