简介

JDK中的Thread大家肯定用过,只要是用过异步编程的同学肯定都熟悉。为了保存Thread中特有的变量,JDK引入了ThreadLocal类来专门对Thread的本地变量进行管理。

ThreadLocal

很多新人可能不明白ThreadLocal到底是什么,它和Thread到底有什么关系。

其实很简单,ThreadLocal本质上是一个key,它的value就是Thread中一个map中存储的值。

每个Thread中都有一个Map, 这个Map的类型是ThreadLocal.ThreadLocalMap。我们先不具体讨论这个ThreadLocalMap到底是怎么实现的。现在就简单将其看做是一个map即可。

接下来,我们看下一个ThreadLocal的工作流程。

首先来看一下ThreadLocal的使用例子:

   public class ThreadId {
// 一个线程ID的自增器
private static final AtomicInteger nextId = new AtomicInteger(0); // 为每个Thread分配一个线程
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
}; // 返回当前线程的ID
public static int get() {
return threadId.get();
}
}

上面的类是做什么用的呢?

当你在不同的线程环境中调用ThreadId的get方法时候,会返回不同的int值。所以可以看做是ThreadId为每个线程生成了一个线程ID。

我们来看下它是怎么工作的。

首先我们调用了ThreadLocal的get方法。ThreadLocal中的get方法定义如下:

    public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

get方法中,我们第一步获取当前的线程Thread,然后getMap返回当前Thread中的ThreadLocalMap对象。

如果Map不为空,则取出以当前ThreadLocal为key对应的值。

如果Map为空,则调用初始化方法:

    private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

初始化方法首先判断当前Thread中的ThreadLocalMap是否为空,如果不为空则设置初始化的值。

如果为空则创建新的Map:

    void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

大家可以看到整个ThreadLocalMap中的Key就是ThreadLocal本身,而Value就是ThreadLocal中定义的泛型的值。

现在我们来总结一下ThreadLocal到底是做什么的。

每个Thread中都有一个ThreadLocal.ThreadLocalMap的Map对象,我们希望向这个Map中存放一些特定的值,通过一个特定的对象来访问到存放在Thread中的这个值,这样的对象就是ThreadLocal。

通过ThreadLocal的get方法,就可以返回绑定到不同Thread对象中的值。

ThreadLocalMap

上面我们简单的将ThreadLocalMap看做是一个map。事实上ThreadLocalMap是一个对象,它里面存放的每个值都是一个Entry.

这个Entry不同于Map中的Entry,它是一个static的内部类:

        static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

注意,这里的Entry继承自WeakReference,表示这个Entry的key是弱引用对象,如果key没有强引用的情况下,会在gc中被回收。从而保证了Map中数据的有效性。

ThreadLocalMap中的值都存放在Entry数组中:

private Entry[] table;

我们看一下怎么从ThreadLocalMap中get一个值的:

        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);
}

key.threadLocalHashCode 可以简单的看做是ThreadLocal代表的key的值。

而 key.threadLocalHashCode & (table.length - 1) 则使用来计算当前key在table中的index。

这里使用的是位运算,用来提升计算速度。实际上这个计算等同于:

key.threadLocalHashCode % table.length

是一个取模运算。

如果按照取模运算的index去查找,找到就直接返回。

如果没找到则会遍历调用nextIndex方法,修改index的值,只到查找完毕为止:

        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;
}

Recycler

ThreadLocal本质上是将ThreadLocal这个对象和不同的Thread进行绑定,通过ThreadLocal的get方法可以获得存储在不同Thread中的值。

归根结底,ThreadLocal和Thread是一对多的关系。因为ThreadLocal在ThreadLocalMap中是弱引用,所以当ThreadLocal被置为空之后,对应的ThreadLocalMap中的对象会在下一个垃圾回收过程中被回收,从而为Thread中的ThreadLocalMap节省一个空间。

那么当我们的Thread是一个长时间运行的Thread的时候,如果在这个Thread中分配了很多生命周期很短的对象,那么会生成很多待回收的垃圾对象,给垃圾回收器造成压力。

为了解决这个问题,netty为我们提供了Recycler类,用来回收这些短生命周期的对象。接下来,我们来探究一下Recycler到底是怎么工作的。

在这之前,我们先看下怎么使用Recycler。

public class MyObject {

  private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
protected MyObject newObject(Recycler.Handle<MyObject> handle) {
return new MyObject(handle);
}
} public static MyObject newInstance(int a, String b) {
MyObject obj = RECYCLER.get();
obj.myFieldA = a;
obj.myFieldB = b;
return obj;
} private final Recycler.Handle<MyObject> handle;
private int myFieldA;
private String myFieldB; private MyObject(Handle<MyObject> handle) {
this.handle = handle;
} public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}
} MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();

本质上,Recycler就像是一个工厂类,通过它的get方法来生成对应的类对象。当这个对象需要被回收的时候,调用Recycler.Handle中的recycle方法,即可将对象回收。

先看一下生成对象的get方法:

    public final T get() {
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
Stack<T> stack = threadLocal.get();
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}

上面代码的含义就是,先判断是否超过了单个线程允许的最大容量,如果是,则返回一个新的对象,绑定一个空的handler,表示这个新创建的对象是不可以被回收的。

如果不是,则从threadLocal中拿到当前线程绑定的Stack。然后从Stack中取出最上面的元素,如果Stack中没有对象,则创建新的对象,并绑定handle。

最后返回handle绑定的对象。

再看一下handle的回收对象方法recycle:

        public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
} Stack<?> stack = this.stack;
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
} stack.push(this);
}

上面的代码先去判断和handle绑定的对象是不是要回收的对象。只有相等的时候才进行回收。

而回收的本质就是将对象push到stack中,供后续get的时候取出使用。

所以,Recycler能够节约垃圾回收对象个数的原因是,它会将不再使用的对象存储到Stack中,并在下次get的时候返回,重复使用。这也就是我们在回收需要重置对象属性的原因:

  public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}

总结

如果你在一个线程中会有多个同类型的短生命周期对象,那么不妨试试Recycle吧。

本文已收录于 http://www.flydean.com/47-netty-thread-…al-object-pool-2/

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

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

可能有人听过ThreadLocal,但一定没人听过ThreadLocal对象池的更多相关文章

  1. 在做关于NIO TCP编程小案例时遇到无法监听write的问题,没想到只是我的if语句的位置放错了位置,哎,看了半天没看出来

    在做关于NIO TCP编程小案例时遇到无法监听write的问题,没想到只是我的if语句的位置放错了位置,哎,看了半天没看出来 贴下课堂笔记: 在Java中使用NIO进行网络TCP套接字编程主要以下几个 ...

  2. 【Python3爬虫】为什么你的博客没人看呢?

    我相信对于很多爱好和习惯写博客的人来说,如果自己的博客有很多人阅读和评论的话,自己会非常开心,但是你发现自己用心写的博客却没什么人看,多多少少会觉得有些伤心吧?我们今天就来看一下为什么你的博客没人看呢 ...

  3. 没人看系列-----html随笔

    <!DOCTYPE> 目录 没人看系列-----html/css详解 前言 不多说这段时间写了好多好多前端的东西,以至于自己重新返回看了一遍前端的所有技术.故此做个总结,准备学东西的请绕行 ...

  4. 没人看系列----css 随笔

    目录 没人看系列----css 随笔 没人看系列----html随笔 前言 没什么要说的就是自己总结,学习用的如果想学点什么东西,请绕行. CSS (Cascading Style Sheets)层叠 ...

  5. c#代码 天气接口 一分钟搞懂你的博客为什么没人看 看完python这段爬虫代码,java流泪了c#沉默了 图片二进制转换与存入数据库相关 C#7.0--引用返回值和引用局部变量 JS直接调用C#后台方法(ajax调用) Linq To Json SqlServer 递归查询

    天气预报的程序.程序并不难. 看到这个需求第一个想法就是只要找到合适天气预报接口一切都是小意思,说干就干,立马跟学生沟通价格. ​ ​不过谈报价的过程中,差点没让我一口老血喷键盘上,话说我们程序猿的人 ...

  6. 给tabhost加上点击监听,不是onTabChanged(String)监听

    给tabhost加上点击监听,不是onTabChanged(String)监听 2012-08-11 01:43 5209人阅读 评论(0) 收藏 举报 stringandroidlayoutnull ...

  7. 为什么你的 App 没人用?请按这8条逐一对照

    为什么你的 App 没人用?请按这8条逐一对照 Kamo Asatryan 可能是这个世界上关注创新生态系统最多的一些人之一,他观察过数百个移动端 App,深入思考过它们的运行机制,并为它们的快速增长 ...

  8. java 监听机制模拟(JButton按钮监听机制)

    一.概念 1.事件监听器 事件监听器就我个人的理解就是:被外部事件(键盘.鼠标)引发的程序,这段程序是被嵌入到事件源的类里面,它负责监听事件列表.而很多人把事件监听器理解成是实现EventListen ...

  9. 零基础想学习C语言,没资源、没人带、不知道从何开始?

    初学编程的小伙伴经常会遇到的问题,1.没资源 2.没人带 3.不知道从何开始 ? 小编也是从新手期过来的,所以很能理解萌新的难处,现在整理一些以前自己学习买来的一些资料送给大家,希望对广大初学小伙伴有 ...

  10. 【Layui__监听button】在form中监听按钮事件

    1. 前言 在使用form表单的按钮时,点击按钮总是页面刷新,代码如下 <button class="layui-btn" lay-submit lay-filter=&qu ...

随机推荐

  1. EF Invalid column name 'Discriminator' Invalid column name 'TagCode'.

    参考资料:Invalid column name 'TagCode'. 该异常和Discriminator没关系,一般原因:1.数据库中字段和实体类字段不一致导致的2.创建新增继承于数据库对应的实体类 ...

  2. GitHub访问地址映射更新的时候刷新DNS

    1.windows系统 上设置地址映射 Window系统本地可以安装 Git Bash 方便本地管理仓,或下载Git 上的代码,在访问Git的时候经常出现Git访问主页加载不了等问题.需要设置在本地设 ...

  3. MYSQL中正则表达式检索数据库

    1.MySQL中使用通配符检索数据库,之外还可以使用正则表达式来检索数据. 使用通配符   '_'  和   '%'的区别如下,   使用通配符的技巧:一般的来说 通配符可以处理数据,但是消耗内存较大 ...

  4. GB 2312字符集:中文编码的基石

    一.GB 2312字符集的背景 GB 2312字符集是中国国家标准委员会于1980年发布的一种中文字符集,是中国大陆最早的中文字符集之一.GB 2312字符集的发布填补了中国大陆中文编码的空白,为中文 ...

  5. URLDNS链分析

    一.概述 URLDNS 是ysoserial中利用链的一个名字,通常用于检测是否存在Java反序列化漏洞.该利用链具有如下特点: 不限制jdk版本,使用Java内置类,对第三方依赖没有要求 目标无回显 ...

  6. 2.4g无线私有协议透传方案特色梳理

    为什么?  在2.4G这个频段,的确有待你拥挤,有提供高速上网的wifi,有提供短距离数据和云音乐传输的bt,还要各种xx的东西.在wifi和bt无法覆盖的领域,又出来一个2.4G私有协议传输芯片,这 ...

  7. Linux 运维工程师面试真题-1-必会Linux 操作系统知识

    Linux 运维工程师面试真题-1-必会Linux 操作系统知识 运维的整个面试流程其实是非常繁杂的,为了方便大家准备,我们特地在这里给大家整理了 一些 Linux 系统运维相关的面试题,有些问题没有 ...

  8. 三维模型3DTile格式轻量化压缩处理效率提高的技术方浅析

    三维模型3DTile格式轻量化压缩处理效率提高的技术方浅析 随着三维模型在各个领域的广泛应用,对于其格式的轻量化压缩处理和效率提高的需求也越发迫切.本文将介绍一些技术方法,帮助实现三维模型3DTile ...

  9. 【LeetCode刷题】剑指Offer 48.最长不含重复字符的子字符串

    剑指Offer 48.最长不含重复字符的子字符串(点击跳转LeetCode) 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度. 示例 1: 输入: "abcab ...

  10. uniapp中引入Leaflet

    1. 引言 uniapp中自带有map组件,并且自带的map组件有常见的显示地图.绘制点线面的功能 但是,它存在以下问题: 收费,自带的map组件使用的是高德.腾讯的地图,无论使用什么样的功能,即使只 ...