ThreadLocal是开发中最常用的技术之一,也是面试重要的考点。本文将由浅入深,介绍ThreadLocal的使用方式、实现原理、内存泄漏问题以及使用场景。

ThreadLocal作用

在并发编程中时常有这样一种需求:每条线程都需要存取一个同名变量,但每条线程中该变量的值均不相同。

如果是你,该如何实现上述功能?常规的思路如下: 使用一个线程共享的Map<Thread,Object>,Map中的key为线程对象,value即为需要存储的值。那么,我们只需要通过map.get(Thread.currentThread())即可获取本线程中该变量的值。

这种方式确实可以实现我们的需求,但它有何缺点呢?——答案就是:需要同步,效率低!

由于这个map对象需要被所有线程共享,因此需要加锁来保证线程安全性。当然我们可以使用java.util.concurrent.*包下的ConcurrentHashMap提高并发效率,但这种方法只能降低锁的粒度,不能从根本上避免同步锁。而JDK提供的ThreadLocal就能很好地解决这一问题。下面来看看ThreadLocal是如何高效地实现这一需求的。

如何使用ThreadLocal

在介绍ThreadLocal原理之前,首先简单介绍一下它的使用方法。

public class Main{private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public void start() {for (int i=0; i<10; i++) {new Thread(new Runnable(){@overridepublic void run(){threadLocal.set(i);threadLocal.get();threadLocal.remove();}}).start();}}
}
复制代码
  • 首先我们需要创建一个线程共享的ThreadLocal对象,该对象用于存储Integer类型的值;
  • 然后在每条线程中可以通过如下方法操作ThreadLocal:
    • set(obj):向当前线程中存储数据
    • get():获取当前线程中的数据
    • remove():删除当前线程中的数据

ThreadLocal的使用方法非常简单,关键在于它背后的实现原理。回到上面的问题:ThreadLocal究竟是如何避免同步锁,从而保证读写的高效?

ThreadLocal实现原理

ThreadLocal的内部结构如下图所示:

ThreadLocal并不维护ThreadLocalMap,并不是一个存储数据的容器,它只是相当于一个工具包,提供了操作该容器的方法,如get、set、remove等。而ThreadLocal内部类ThreadLocalMap才是存储数据的容器,并且该容器由Thread维护。

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。

ThreadLocalMap由一个个Entry对象构成,Entry的代码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
复制代码

Entry继承自WeakReference<ThreadLocal<?>>,一个EntryThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

那么,ThreadLocal是如何工作的呢?下面来看set和get方法。

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}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();
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
复制代码

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

为何要使用弱引用?

对弱引用不了解的同学可以参考笔者的另一篇文章:http://blog.csdn.net/u010425776/article/details/50760053。

Java设计之初的一大宗旨就是——弱化指针。 Java设计者希望通过合理的设计简化编程,让程序员无需处理复杂的指针操作。然而指针是客观存在的,在目前的Java开发中也不可避免涉及到“指针操作”。如:

Object a = new Object();
复制代码

上述代码创建了一个强引用a,只要强引用存在,垃圾收集器是不会回收该对象的。如果该对象非常庞大,那么为了节约内存空间,在该对象使用完成后,我们需要手动拆除该强引用,如下面代码所示:

a = null;
复制代码

此时,指向该对象的强引用消除了,垃圾收集器便可以回收该对象。但在这个过程中,仍然需要程序员处理指针。为了弱化指针这一概念,弱引用便出现了,如下代码创建了一个Person类型的弱引用:

WeakReference<Person> wr = new WeakReference<Person>(new Person());
复制代码

此时程序员不用再关注指针,只要没有强引用指向Person对象,垃圾收集器每次运行都会自动将该对象释放。

那么,ThreadLocalMap中的key使用弱引用的原因也是如此。当一条线程中的ThreadLocal对象使用完毕,没有强引用指向它的时候,垃圾收集器就会自动回收这个Key,从而达到节约内存的目的。

那么,问题又来了——这会导致内存泄漏问题!

ThreadLocal的内存泄漏问题

在ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时value和value指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!

不过不用担心,ThreadLocal提供了这个问题的解决方案。

每次操作set、get、remove操作时,ThreadLocal都会将key为null的Entry删除,从而避免内存泄漏。

那么问题又来了,如果一个线程运行周期较长,而且将一个大对象放入LocalThreadMap后便不再调用set、get、remove方法,此时该仍然可能会导致内存泄漏。

这个问题确实存在,没办法通过ThreadLocal解决,而是需要程序员在完成ThreadLocal的使用后要养成手动调用remove的习惯,从而避免内存泄漏。

ThreadLocal的使用场景

Web系统Session的存储就是ThreadLocal一个典型的应用场景。

Web容器采用线程隔离的多线程模型,也就是每一个请求都会对应一条线程,线程之间相互隔离,没有共享数据。这样能够简化编程模型,程序员可以用单线程的思维开发这种多线程应用。

当请求到来时,可以将当前Session信息存储在ThreadLocal中,在请求处理过程中可以随时使用Session信息,每个请求之间的Session信息互不影响。当请求处理完成后通过remove方法将当前Session信息清除即可。

揭秘ThreadLocal相关推荐

  1. 2019年Java大厂面试(吐血超详细总结)

    本文来自于慕课网手记: Java大厂面试(吐血超详细总结) 作者:小码哥的freestyle 链接: https://www.imooc.com/article/286545 来源:慕课网 面试清单 ...

  2. 转 :2019年Java大厂面试(吐血超详细总结)

    2019年Java大厂面试(吐血超详细总结) 本文来自于慕课网手记:Java大厂面试(吐血超详细总结),转载请保留链接 ;) 转载自:https://www.imooc.com/article/286 ...

  3. ThreadLocal的短板,我TTL来补

    之前我已经分析了ThreadLocal.InheritableThreadLocal.FastThreadLocal. 然后有小伙伴让我再说说TransmittableThreadLocal(下边统一 ...

  4. BAT这样的大公司为什么面试经常拿ThreadLocal考验求职者

    什么是ThreadLocal ThreadLocal是一个本地线程副本变量工具类,各个线程都拥有一份线程私有的数据,线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用. ThreadLoca ...

  5. (Java多线程常见面试题)ThreadLocal 是什么?有哪些使⽤场景?

    最近在研究多线程项目时,无意间看到一个很有意思的Java类----ThreadLocal.于是乎一向对于新东西充满好奇的我又开始了一系列深挖细究,在经过学习和参考网上其他大佬的见解后,现将自己的理解作 ...

  6. FastThreadLocal吞吐量居然是ThreadLocal的3倍

    目前关于FastThreadLocal的很多文章都有点老有点过时了(本文将澄清几个误区),很多文章关于FastThreadLocal介绍的也不全,希望本篇文章可以带你彻底理解FastThreadLoc ...

  7. 康复治疗学可以考计算机吗,【大揭秘】2018“人机对话”康复医学治疗技术专业技术资格考试...

    原标题:[大揭秘]2018"人机对话"康复医学治疗技术专业技术资格考试 昨天,关于"2018年康复医学治疗技术专业技术资格考试采用人机对话考试方式"的通知一经发 ...

  8. 正确理解ThreadLocal

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt107 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的, ...

  9. 零代价修复海量服务器的内核缺陷——UCloud内核热补丁技术揭秘

    下述为UCloud资深工程师邱模炯在InfoQ架构师峰会上的演讲--<UCloud云平台的内核实践>中非常受关注的内核热补丁技术的一部分.给大家揭开了UCloud云平台内核技术的神秘面纱. ...

最新文章

  1. 带边框有点击事件的表格
  2. linux降内核版本_ubuntu18.04 降内核版本的问题
  3. Oracle怎样创建共享文件夹,Oracle vm要如何使用共享文件夹的解决方法
  4. 机器学习(MACHINE LEARNING) 【周志华版-”西瓜书“-笔记】 DAY5-神经网络
  5. 树网的核(codevs 1167)
  6. Cython入门.VS.C++
  7. 误人子弟的网络,谈谈HTTP协议中的短轮询、长轮询、长连接和短连接(转载)
  8. 如何设计一个高可用的运营系统
  9. Java-IO-对接流
  10. 单例模式——饿汉式和懒汉式
  11. 二叉树数据结构和算法
  12. Python机器学习:多项式回归与模型泛化003过拟合与欠拟合
  13. es6,es7,es8语法总结
  14. 不用计算机怎么连接无线,不用电脑可以装wifi吗 不通过电脑装wifi方法【图文】...
  15. Could not resolve type alias 解决方法
  16. python机械臂仿真_机械臂 python
  17. VS2010在加载项目时,提示无法打开项目文件, 此安装不支持该项目类型的解决方法
  18. 如何诊断SQL数据?
  19. Linux引导故障和修复进入系统
  20. android 是否可触摸,android 2.0可能支持多点触摸?

热门文章

  1. boost::copy_graph用法的测试程序
  2. Boost:基于Boost的stream流服务器
  3. VTK:网格之Subdivision
  4. VTK:相互作用之DoubleClick
  5. VTK:几何对象之OpenVRCone
  6. QDoc特殊内容special content
  7. Qt Creator用户界面
  8. C++虚析构和纯虚析构
  9. c++享元模式flyweight
  10. linux命令实验设备,实验二 Linux系统的常用命令