Java之ThreadLocal
Java之ThreadLocal
什么是ThreadLocal
注意:JDK1.8版本
首先看下ThreadLocal在源码中的介绍:
1 | /** |
我们可以这样理解:ThreadLocal提供了线程的局部变量,每个线程都可以通过set()**和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离**。
简而言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
ThreadLocal 的主要是封装变量起到线程隔离的作用,另外 ThreadLocal 通常会用 private 和 static 来修饰。
##ThreadLocal的使用场景
根据 ThreadLocal 线程隔离的特性,主要有以下几种场景:
- 保存一个多层调用的参数。想象一下这样的场景,有一个变量,需要通过很多层调用才能最终使用到,但是这中间的每一层方法调用都需要传递这个变量作为方法的入参,这样写虽然可以达到目的,但是却不够美观,如果可以用到 ThreadLocal,随用随取,就会优雅很多。
- 保存上下文信息,如知名的 APM 系统 SkyWalking 就用到了 ThreadLocal 来保存 context,传递调用信息,还有 log4j 的 MDC 机制,也是基于 ThreadLocal 来实现的。
- 由于 ThreadLocal 保存的变量是每个线程私有的,所以也可以一定程度上保证线程安全,比如 SimpleDateFormat 就是线程不安全的,所以生产环境上就经常使用 ThreadLocal 来封装。
使用简单案例
1 | public class UserContext { |
主要是三个方法,set,get 以及 remove。不要忘记 remove,不然容易造成内存泄漏。
ThreadLocal实现的原理
首先,看下ThreadLocal的set()方法,一般使用都是new完对象,就往里面set对象了
1 | public void set(T value) { |
上面有个ThreadLocalMap,看一下这个是什么
1 | static class ThreadLocalMap { |
通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储。
值都是存储到这个Map上的,key是当前ThreadLocal对象!
如果该Map不存在,则初始化一个:
1 | void createMap(Thread t, T firstValue) { |
如果该Map存在,则从Thread中获取!
1 | /** |
Thread维护了ThreadLocalMap变量
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
从上面又可以看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中!
于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象
查看get()方法:
1 | public T get() { |
ThreadLocal原理总结
- 每个Thread维护着一个ThreadLocalMap的引用
- ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
- 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
- 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
- ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响。
避免内存泄露
在使用 ThreadLocal 的时候,ThreadLocal 变量作为 key(实际上是一个弱引用),实际存储的值作为 value,存储到了 ThreadLocalMap 中,如下图,图中的虚线表示弱引用。
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
想要避免内存泄露就要手动remove()掉!
注意这里的 ThreadLocalMap 中的 Entry 继承了 WeakReference,也就是弱引用。至于弱引用,可以参考《深入理解 Java 虚拟机》书中四种引用强、软、弱、虚的定义:
- 强引用类似
Object obj = new Objecg()
这类,只要有就不会被回收。 - 软引用用来描述一些还有用但并非必需的对象。在内存溢出之际,会把这些对象列进回收范围进行第二次回收。如果回收后还不够,才会抛出内存溢出异常。
- 弱引用也是描述非必须对象的,但比软引用更弱。垃圾收集器工作时即使内存够用也会回收。
- 虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。被虚引用关联的对象呗收集器回收时会收到一个系统通知。