ThreadLocal
提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。
如何使用ThreadLocal
1 | public class Main { |
首先需要创建一个线程共享的ThreadLocal
对象,该对象用于存储Integer
类型的值,然后在每条线程中调用以下方法操作ThreadLocal
:
set(obj)
:向当前线程中存储数据get()
:获取当前线程中的数据remove()
:删除当前线程中的数据
ThreadLocal实现原理
ThreadLocal
并不维护ThreadLocalMap
,它只是相当于一个工具包,提供了操作该容器的方法,如get
、set
、remove
等。而ThreadLocal
内部类ThreadLocalMap
才是存储数据的容器,并且该容器由Thread
维护。
每一个Thread
对象均含有一个ThreadLocalMap
类型的成员变量threadLocals
,它存储本线程中所有ThreadLocal
对象及其对应的值。
ThreadLocalMap
由一个个Entry
对象构成,Entry
的代码如下:
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
Entry
继承自WeakReference<ThrealLocal<?>>
,一个Entry
由ThreadLocal
对象和Object
构成。由此可见,Entry
的key
是ThreadLocal
对象,并且是一个弱引用。当没指向key
的强引用后,该key
就会被垃圾收集器回收。
ThreadLocal
的set
和get
方法:
1 | public void set(T value) { |
当执行set
方法时,ThreadLocal
首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap
对象。再以当前ThreadLocal
对象为key
,将值存储进ThreadLocalMap
对象中。
get
方法执行过程类似。ThreadLocal
首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap
对象。再以当前ThreadLocal
对象为key
,获取对应的value
。
由于每一条线程均含有各自私有的ThreadLocalMap
容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
ThreadLocal的内存泄露问题
在ThreadLocalMap
中,只有key
是弱引用,value
仍然是一个强引用。当某一条线程中的ThreadLocal
使用完毕,没有强引用指向它的时候,这个key
指向的对象就会被垃圾收集器回收,从而这个key
就变成了null
;然而,此时value
和value
指向的对象之间仍然是强引用关系,只要这种关系不解除,value
指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!
不过不用担心,ThreadLocal
提供了这个问题的解决方案。
每次操作set
、get
、remove
操作时,ThreadLocal
都会将key
为null
的Entry
删除,从而避免内存泄漏。
那么问题又来了,如果一个线程运行周期较长,而且将一个大对象放入ThreadLoalMap
后便不再调用set
、get
、remove
方法,此时该仍然可能会导致内存泄漏。
这个问题确实存在,没办法通过ThreadLocal
解决,而是需要程序员在完成ThreadLocal
的使用后要养成手动调用remove
的习惯,从而避免内存泄漏。