ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

从数据结构入手

下图为ThreadLocal的内部结构图

 
ThreadLocal结构内部

从上面的结构图,我们已经窥见ThreadLocal的核心机制:

  • 每个Thread线程内部都有一个Map。
  • Map里面存储线程本地对象(key)和线程的变量副本(value)
  • 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

Thread线程内部的Map在类中描述如下:

  1. public class Thread implements Runnable {
  2. /* ThreadLocal values pertaining to this thread. This map is maintained
  3. * by the ThreadLocal class. */
  4. ThreadLocal.ThreadLocalMap threadLocals = null;
  5. }

深入解析ThreadLocal

ThreadLocal类提供如下几个核心方法:

  1. public T get()
  2. public void set(T value)
  3. public void remove()
  • get()方法用于获取当前线程的副本变量值。
  • set()方法用于保存当前线程的副本变量值。
  • initialValue()为当前线程初始副本变量值。
  • remove()方法移除当前前程的副本变量值。

get()方法

  1. /**
  2. * Returns the value in the current thread's copy of this
  3. * thread-local variable. If the variable has no value for the
  4. * current thread, it is first initialized to the value returned
  5. * by an invocation of the {@link #initialValue} method.
  6. *
  7. * @return the current thread's value of this thread-local
  8. */
  9. public T get() {
  10. Thread t = Thread.currentThread();
  11. ThreadLocalMap map = getMap(t);
  12. if (map != null) {
  13. ThreadLocalMap.Entry e = map.getEntry(this);
  14. if (e != null)
  15. return (T)e.value;
  16. }
  17. return setInitialValue();
  18. }
  19. ThreadLocalMap getMap(Thread t) {
  20. return t.threadLocals;
  21. }
  22. private T setInitialValue() {
  23. T value = initialValue();
  24. Thread t = Thread.currentThread();
  25. ThreadLocalMap map = getMap(t);
  26. if (map != null)
  27. map.set(this, value);
  28. else
  29. createMap(t, value);
  30. return value;
  31. }
  32. protected T initialValue() {
  33. return null;
  34. }

步骤:
1.获取当前线程的ThreadLocalMap对象threadLocals
2.从map中获取线程存储的K-V Entry节点。
3.从Entry节点获取存储的Value副本值返回。
4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。

set()方法

  1. /**
  2. * Sets the current thread's copy of this thread-local variable
  3. * to the specified value. Most subclasses will have no need to
  4. * override this method, relying solely on the {@link #initialValue}
  5. * method to set the values of thread-locals.
  6. *
  7. * @param value the value to be stored in the current thread's copy of
  8. * this thread-local.
  9. */
  10. public void set(T value) {
  11. Thread t = Thread.currentThread();
  12. ThreadLocalMap map = getMap(t);
  13. if (map != null)
  14. map.set(this, value);
  15. else
  16. createMap(t, value);
  17. }
  18. ThreadLocalMap getMap(Thread t) {
  19. return t.threadLocals;
  20. }
  21. void createMap(Thread t, T firstValue) {
  22. t.threadLocals = new ThreadLocalMap(this, firstValue);
  23. }

步骤:
1.获取当前线程的成员变量map
2.map非空,则重新将ThreadLocal和新的value副本放入到map中。
3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中。

remove()方法

  1. /**
  2. * Removes the current thread's value for this thread-local
  3. * variable. If this thread-local variable is subsequently
  4. * {@linkplain #get read} by the current thread, its value will be
  5. * reinitialized by invoking its {@link #initialValue} method,
  6. * unless its value is {@linkplain #set set} by the current thread
  7. * in the interim. This may result in multiple invocations of the
  8. * <tt>initialValue</tt> method in the current thread.
  9. *
  10. * @since 1.5
  11. */
  12. public void remove() {
  13. ThreadLocalMap m = getMap(Thread.currentThread());
  14. if (m != null)
  15. m.remove(this);
  16. }
  17. ThreadLocalMap getMap(Thread t) {
  18. return t.threadLocals;
  19. }

remove方法比较简单,不做赘述。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。

 
ThreadLocalMap类图

在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。

  1. static class Entry extends WeakReference<ThreadLocal> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。

ThreadLocalMap的成员变量:

  1. static class ThreadLocalMap {
  2. /**
  3. * The initial capacity -- MUST be a power of two.
  4. */
  5. private static final int INITIAL_CAPACITY = 16;
  6. /**
  7. * The table, resized as necessary.
  8. * table.length MUST always be a power of two.
  9. */
  10. private Entry[] table;
  11. /**
  12. * The number of entries in the table.
  13. */
  14. private int size = 0;
  15. /**
  16. * The next size value at which to resize.
  17. */
  18. private int threshold; // Default to 0
  19. }

Hash冲突怎么解决

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

  1. /**
  2. * Increment i modulo len.
  3. */
  4. private static int nextIndex(int i, int len) {
  5. return ((i + 1 < len) ? i + 1 : 0);
  6. }
  7. /**
  8. * Decrement i modulo len.
  9. */
  10. private static int prevIndex(int i, int len) {
  11. return ((i - 1 >= 0) ? i - 1 : len - 1);
  12. }

显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。

所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

ThreadLocalMap的问题

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免泄漏
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

  1. ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
  2. try {
  3. threadLocal.set(new Session(1, "Misout的博客"));
  4. // 其它业务逻辑
  5. } finally {
  6. threadLocal.remove();
  7. }

应用场景

还记得Hibernate的session获取场景吗?

  1. private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
  2. //获取Session
  3. public static Session getCurrentSession(){
  4. Session session = threadLocal.get();
  5. //判断Session是否为空,如果为空,将创建一个session,并设置到本地线程变量中
  6. try {
  7. if(session ==null&&!session.isOpen()){
  8. if(sessionFactory==null){
  9. rbuildSessionFactory();// 创建Hibernate的SessionFactory
  10. }else{
  11. session = sessionFactory.openSession();
  12. }
  13. }
  14. threadLocal.set(session);
  15. } catch (Exception e) {
  16. // TODO: handle exception
  17. }
  18. return session;
  19. }

为什么?每个线程访问数据库都应当是一个独立的Session会话,如果多个线程共享同一个Session会话,有可能其他线程关闭连接了,当前线程再执行提交时就会出现会话已关闭的异常,导致系统异常。此方式能避免线程争抢Session,提高并发下的安全性。

使用ThreadLocal的典型场景正如上面的数据库连接管理,线程会话管理等场景,只适用于独立变量副本的情况,如果变量为全局共享的,则不适用在高并发下使用。

总结

  • 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
  • ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
  • 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。

推荐阅读

推荐一款在线画图工具

Processon在线画图工具,一款很好用的画图工具

作者:Misout
链接:https://www.jianshu.com/p/98b68c97df9b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

ThreadLocal 原理的更多相关文章

  1. ThreadLocal原理及其实际应用

    前言 java猿在面试中,经常会被问到1个问题: java实现同步有哪几种方式? 大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的Th ...

  2. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  3. 简析ThreadLocal原理及应用

    简析ThreadLocal原理及应用 原创: 东晨雨 JAVA万维猿圈 4月17日 ThreadLocal的源码加上注释不超过八百行,源码结构清晰,代码也比较简洁.ThreadLocal可以说是Jav ...

  4. ThreadLocal原理简单刨析

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

  5. ThreadLocal原理与模拟

    首先用一个程序模拟一下ThreadLocal: public class ThreadLocal1 { private static Dictionary<Thread, Integer> ...

  6. java concurrency in practice读书笔记---ThreadLocal原理

    ThreadLocal这个类很强大,用处十分广泛,可以解决多线程之间共享变量问题,那么ThreadLocal的原理是什么样呢?源代码最能说明问题! public class ThreadLocal&l ...

  7. ThreadLocal原理及使用示例

    简介:本文已一个简要的代码示例介绍ThreadLocal类的基本使用方式,在此基础上结合图片阐述它的内部工作原理. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblog ...

  8. 聊聊ThreadLocal原理以及使用场景-JAVA 8源码

    相信很多人知道ThreadLocal是针对每个线程的,但是其中的原理相信大家不是很清楚,那咱们就一块看一下源码. 首先,我们先看看它的set方法.非常简单,从当前Thread中获取map.那么这个ge ...

  9. ThreadLocal 原理和使用场景分析

    ThreadLocal 不知道大家有没有用过,但至少听说过,今天主要记录一下 ThreadLocal 的原理和使用场景. 使用场景 直接定位到 ThreadLocal 的源码,可以看到源码注释中有很清 ...

  10. ThreadLocal原理分析与使用场景

    什么是ThreadLocal变量 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.这里有几点需要注意: 因为每个 Thr ...

随机推荐

  1. java web20套项目

    http://shenghuo.lshou.com/c4154/t5bdbcc98b9a9.html

  2. robot framework使用小结(一)

    项目组要用到robot framework验收web,因此花了两天时间了解了一下这个框架.我把网上各位大侠分享的内容整理成一个小小demo,参考的出处没有列出来,在此一并感谢各位. demo仍旧是打开 ...

  3. yum只下载不安装

    1 修改/etc/yum.conf的keepcache=1 [root@136 packages]# vi /etc/yum.conf keepcache=1 2 安装yum-utils包 [root ...

  4. DOM-BOM-EVENT(5)

    5.宽.高.位置相关 5.1.clientX/clientY clientX和clientY表示鼠标在浏览器可视区的坐标位置 <script> document.onclick = fun ...

  5. 关于阿里云服务器Linux安装Tomcat后,外网不能访问解决方案

    这里需要提及三个方面的问题   第一个方面:Linux上启动防火墙的问题 当下比较流行的Linux镜像是CentOS,所以防火墙也随之变成了firewall,那么怎么操作这个防火墙呢?   #停止fi ...

  6. div嵌套引起的内层margin-top对外层div起作用

    嵌套div中margin-top转移问题的解决办法在这两个浏览器中,有两个嵌套关系的div,如果外层div的父元素padding值为0,那么内层div的margin-top或者margin-botto ...

  7. python设计模式之模版方法设计模式

    我们在使用python的flask框架时,可能会经常用到生命周期函数如:before_request, before_first_request,或者信号等,刚开始学的时候就想只要写一个函数,然后加上 ...

  8. 一.django初识

    1.创建django项目:[vagrant@CentOS7 vagrant]$ django-admin startproject devops [vagrant@CentOS7 vagrant]$ ...

  9. 基于git的博客(含站点与小程序)

    1 效果 静态站点: blog.makergyt.com 备用链接: github.blog.makergyt.com 小程序: 语雀:<MakerGYT blog> 2 需求分析 2.1 ...

  10. POJ2362贪心

    题意:我们的化学生物学家发明了一种新的叫stripies非常神奇的生命.如果一个质量为m1和m2的stripies相撞,生成的stripies体重是2*sqrt(m1*m2) 现在,科学家们想知道,如 ...