上节讲过了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. jQuery-ajax-.load方法

    使用jQuery封装的ajax是非常好用的,这个里面提供了几个比较好用的方法. load(url[,data, callback])方法: 说明:这个是jQuery中的最底层方法$.ajax()封装的 ...

  2. 手动搭建自己的nuget服务器及使用

    这篇文章的主要目的: 1.搭建自己的私有的nuget服务器 2.打包代码为nuget包 3.在其他项目中使用私有服务器上的nuget包 一. 搭建自己的nuget服务器 1. 创建一个空的ASP.NE ...

  3. Singleton and Prototype Bean Scope in Spring

    Scope描述的是Spring容器如何新建Bean的实例的. 1> Singleton: 一个Spring容器只有一个Bean的实例,此为Spring的默认配置,全容器共享一个实例. 2> ...

  4. hdoj1009 FatMouse' Trade——贪心算法

    贪心思路:按单位猫粮能兑换到的javaBean从大到小将组合进行排序,总是在当前兑换尽可能多的javabeans 问题描述:点击打开链接 hdoj1009 FatMouse's Trade 源代码: ...

  5. 戴尔R720安装ESXI系统

    1.U盘安装系统,使用UltraISO制作启动盘 参考地址:https://jingyan.baidu.com/article/5225f26b0bb45fe6fa0908bc.html 2.插上U盘 ...

  6. 学习 GitHub 有什么好处?

    layout: post title: "学习 GitHub 有什么好处?" date: 2018-04-15 19:20:20 +0800 --- 鸣谢:王顶 老师(河北经贸大学 ...

  7. 如何安装xenserver

    xenserver安装 选择键盘 是否同意协议 清理磁盘 选择本地磁盘安装 选择本地镜像文件 输入管理密码 配置IP地址 配置DNS服务器地址 选择地点 配置NTP服务器地址 开始安装 安装完成

  8. smarty 截取字符串

    在视图中使用smarty来截取字符串的方法:          {$array|truncate:15:"...":true}   php:控制器中 $index['content ...

  9. .Net Core 使用百度UEditor编辑器

    一.准备文件 1. 下载UEditor官方版本.删除其中后端文件.保留后端文件夹中的config.json文件 2. 在NuGet管理器中搜索UEditorNetCore,拿到项目地址,下载源码 下载 ...

  10. VSCode 云同步扩展设置 Settings Sync 插件

    VSCode 云同步扩展设置 Settings Sync 插件 Hi.大家好,今天又是美好的一天. 关于 Settings Sync扩展: Settings Sync可以同步你当前的VSCode配置环 ...