一、背景

最近有人问我ThreadLocal是如何做到在每个线程中的值都是隔离的,此处写篇文章来简单记录下。

二、ThreadLocal解决的问题

  1. 该数据属于该线程Thread自身,别的线程无法对其影响。(需要注意:需要调用ThreadLocal的remove方法)
  2. 不存在线程安全问题。(因为ThreadLocal类型的变量只有自身的线程可以访问,所以这点是成立的。)

比如:

用户登录成功后,需要将登录用户信息保存起来,以方便在系统中的任何地方都可以使用到,那么此时就可以使用ThreadLocal来实现。例如:Spring Security中的ThreadLocalSecurityContextHolderStrategy类。

三、如何创建一个ThreadLocal实例

private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();

ThreadLocal的实例推荐使用private static final来修饰。

四、ThreadLocal如何做到线程变量隔离

1、理解3个类

  1. ThreadLocal: 此类提供了一个简单的set,get,remove方法,用于设置,获取或移除 绑定到线程本地变量中的值。

  2. ThreadLocalMap: 这是在ThreadLocal中定义的一个类,可以简单的将它理解成一个Map,不过它的key是WeakReference弱引用类型,这样当这个值没有在别的地方引用时,在发生垃圾回收时,这个map的key会被自动回收,不过它的值不会被自动回收。

    static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
    // key 弱引用
    super(k);
    // 值强引用
    value = v;
    }
    }
  3. Thread:这个是线程类,在这个类中存在一个threadLocals变量,具体的类型是ThreadLocal.ThreadLocalMap

2、看下set方法是如何实现的

public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取绑定到这个线程自身的 ThreadLocalMap,这个ThreadLocalMap是从Thread类的`threadLocals`变量中获取的
ThreadLocalMap map = getMap(t);
if (map != null) {
// 向map中设置值,key为 ThreadLocal 对象的实例。
map.set(this, value);
} else {
// 如果map不存在,则创建出来。
createMap(t, value);
}
}

通过上方的代码,我们可知: 当我们向ThreadLocal中设置一个值,会经过如下几个步骤:

  1. 获取当前线程Thread
  2. 获取当前线程的 ThreadLocalMap 对象。
  3. ThreadLocalMap中设置值,key为ThreadLocal对象,值为具体的值。

3、看看 get 方法如何实现

public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取这个线程自身绑定的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// this是ThreadLocal对象,获取Map中的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取具体的值
T result = (T)e.value;
return result;
}
}
// 设置初始值
return setInitialValue();
}

从上方的get 和 set 方法可以得知,通过往ThreadLocal对象中设置值或获取值,其实是最终操作到Thread对象中的threadLocals字段中,而这个字段是Thread自身的,因此做到了隔离。

五、ThreadLocalMap中的hash冲突是如何处理的

1、ThreadLocal对象的hash值是怎样的

private final int threadLocalHashCode = nextHashCode();
// 该 ThreadLocal 对象自身的hash code值
private final int threadLocalHashCode = nextHashCode();
// 从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每次递增固定的值
private static final int HASH_INCREMENT = 0x61c88647;
// hash code 值计算
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

从上方的代码中可以,ThreadLocal类在实例化出来之后,它的hash code值(threadLocalHashCode)就是固定的,即使ThreadLocal调用了set方法,设置了别的值,它的hash code值也不会发生变化。

此字段threadLocalHashCodeThreadLocal对象的hash值,在ThreadLocalMap中需要用到这个hash值。

2、解决hash冲突

ThreadLocalMap解决hash冲突的办法很简单。就是通过线性探测法。如果发生了冲突,就去找数组后面的可用位置。具体看上图。演示的是A和B 2个ThreadLocal对象,然后发生了冲突,A和B存在的位置在那个地方。

六、ThreadLocal内存泄漏

ThreadLocal为什么会存在内存泄漏呢?

这是因为ThreadLocalMap中的keyWeakReference类型,也就是弱引用类型,而弱引用类型的数据在没有外部强引用类型的话,在发生gc的时候,会自动被回收掉。注意: 此时是key被回收了,但是value是没有回收的。因此在ThreadLocalMap中的Entry[]中可能存在keynull,但是value是具体的值的对象,因此就发生了内存泄漏。

解决内存泄漏:

当我们使用完ThreadLocal对象后,需要在适当的时机调用ThreadLocal#remove()方法。 否则就只有等Thread自动退出才能清除,如果是使用了线程池,Thread会重用,清除的机会就更难。

ThreadLocal的简单理解的更多相关文章

  1. 对ThreadLocal的一些理解

    ThreadLocal也是在面试过程中经常被问到的,本文主要从以下三个方面来谈对ThreadLocal的一些理解: ThreadLocal用在什么地方 ThreadLocal一些细节 ThreadLo ...

  2. ThreadLocal原理简单刨析

    ThreadLocal原理简单刨析 ThreadLocal实现了各个线程的数据隔离,要知道数据是如何隔离的,就要从源代码分析. ThreadLocal原理 需要提前说明的是:ThreadLocal只是 ...

  3. git的简单理解及基础操作命令

    前端小白一枚,最近开始使用git,于是花了2天看了廖雪峰的git教程(偏实践,对于学习git的基础操作很有帮助哦),也在看<git版本控制管理>这本书(偏理论,内容完善,很不错),针对所学 ...

  4. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  5. [转]简单理解Socket

    简单理解Socket 转自 http://www.cnblogs.com/dolphinX/p/3460545.html  题外话 前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公 ...

  6. Js 职责链模式 简单理解

    js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = funct ...

  7. Deep learning:四十六(DropConnect简单理解)

    和maxout(maxout简单理解)一样,DropConnect也是在ICML2013上发表的,同样也是为了提高Deep Network的泛化能力的,两者都号称是对Dropout(Dropout简单 ...

  8. Deep learning:四十二(Denoise Autoencoder简单理解)

    前言: 当采用无监督的方法分层预训练深度网络的权值时,为了学习到较鲁棒的特征,可以在网络的可视层(即数据的输入层)引入随机噪声,这种方法称为Denoise Autoencoder(简称dAE),由Be ...

  9. 简单理解dropout

    dropout是CNN(卷积神经网络)中的一个trick,能防止过拟合. 关于dropout的详细内容,还是看论文原文好了: Hinton, G. E., et al. (2012). "I ...

随机推荐

  1. 初学Java时没有理解的一些概念

    背景 之前学Java属于赶鸭子上架,草草学习基础语法便直接做课程作业,许多概念问题仍不清楚,故在此梳理一下,主要参考廖雪峰和互联网资料. Java运行方式与JVM Java是介于编译型语言(C++)和 ...

  2. 从零开始,开发一个 Web Office 套件(15):拖动边框,平移编辑器

    这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...

  3. 关于 background-image 渐变gradient()那些事!

    大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...

  4. 数据结构篇(2) ts实现单循环链表

    JS的class可以通过extends关键字实现类似其他语言的继承效果,比起使用一个extends关键字,在es5中实现继承要复杂一些,可以通过修改原型链的方法实现继承,让一个原型对象等于另一个类型的 ...

  5. stm32F103RCT6的DMA使用经历

    ​ DMA可以直接传输数据,减少了CPU的负担,是个很好的功能,但是用的时候难免会一头雾水.这次做个小小的串口收发程序就碰到了许多问题. 之前没有注意,选择了DMA的circular模式,然后奇怪的事 ...

  6. Blazor和Vue对比学习(基础1.4):事件和子传父

    Blazor和Vue的组件事件,都使用事件订阅者模式.相对于上一章的组件属性,需要多绕一个弯,无论Blazor还是Vue,都是入门的第一个难点.要突破这个难点,一是要熟悉事件订阅模式<其实不难& ...

  7. DOM操作标签,事件绑定,jQuery框架

    DOM操作标签 ''' 在起变量名的时候 如果该变量指向的是一个标签 那么建议使用 xxxEle eg:aEle\pEle\divEle\spanEle ''' 基本使用 动态创建一个标签 var 变 ...

  8. zabbix 1.2

    1.zabbix图形界面乱码问题处理 2.自定义监控项 (1)在agent端配置agent.conf    打开vim /etc/zabbix/zabbix-agent.conf    找到UserP ...

  9. 好客租房9-jsx的学习目标

    1能够知道什么是jsx 2能够使用jsx创建react元素 3能够在jsx使用javascript表达式 4能够使用jsx的条件渲染和列表渲染 5能够给jsx添加样式 jsx的基本使用 jsx中使用j ...

  10. 125_Power BI 中 DAX 的性能测试

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 最近在看DAX"圣经"第二版<The Definitive Guide to DAX ...