述
上文中了解了 ThreadLocal
的常用场景和最基本的用法之后再来看一下它的原理以及一些要注意的事项
Thread,ThreadLocal,ThreadLocalMap
首先需要了解一下 Thread
,ThreadLocal
,ThreadLocalMap
这三个类之间的关系,如图
就是一个线程 (Thread) 对应有一个 ThreadLocalMap
, ThreadLocalMap
中以键值对的形式存放着一堆的 ThreadLocal
我们可以看一下具体对应的代码
Thread类:
然后再点进去 ThreadLocalMap
类里
可以看到 ThreadLocalMap
这里存储的是一个键值对 Entry
类型的
重要方法解析
initialValue
,设置初始化值的方法 。
1.该方法会返回当前线程的“初始值”,这是一个延迟加载的方法,之后第一次调用get()的时候,才触发 。
2.当线程第一次使用get方法访问变量的时候,将调用 initialValue
方法,除非线程先前调用了set方法,这样的话,不会在调用initialValue
方法。
get方法是先取出当前线程的ThreadLocalMap
,然后调用map.getEntry
方法,把本ThreadLocal
的引用作为参数传入,取出map中属于本ThreadLocal
的value
首先看一下源码
public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的 ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//获取ThreadLocal的值T result = (T)e.value;return result;}}//调用初始化方法return setInitialValue();}
private T setInitialValue() {//调用初始化方法,获取返回值T value = initialValue();//获取当前线程Thread t = Thread.currentThread();//根据线程 获取对应 ThreadLocalMap ThreadLocalMap map = getMap(t);// 如果map不为空,那么将初始化后的值也就是ThreadLocal放入mapif (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
总体来说这个方法就是给 ThreadLocal
去设置值的, 这里首先会调用 initialValue
这个方法,这个是不是很熟悉, 就是我们上文中的案例中初始化 ThreadLocal
时,重写的方法,默认是返回空的
先去Map中找,没有的话再去初始化,从这里也可以看出,我们设置的初始化值是懒加载的
还有两个常用的方法就是 set()
和 remove()
set
()方法
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)//this 是当前的ThreadLocalmap.set(this, value);elsecreateMap(t, value);}
注意事项
内存泄露
使用 ThreadLocal
应该注意内存泄露的风险
内存泄露是对象已经没用了, 但是占用的内存不能被GC回收 ,导致占用越来越多,内存不够用了
为什么会出现内存泄露我们来看一下
这里看一下存放键值对的这个 Entry
,它是继承了一个 WeakReference
用来存放key,所以key是弱引用类型的,value是强引用类型的。
弱引用的特点是如果这个对象只被弱引用关联,那么这个对象就会被回收,所以这里的key是可以被回收掉的,但是 value 是个强引用,它不会被回收掉
所以 ThreadLocalMap
的每个 Entry
都包含了一个key的弱引用,和一个value的强引用, 正常情况下,当线程终止保存在 ThreadLocal
中的 value 就会被回收, 但是如果一个线程要持续很久,那它的value就不会被回收掉,即使这个value已经没用了 。比如说线程池,其实是一个线程反复使用,如果说线程停止,那么其中ThreadLocal的value是有可能不被回收的。
所以因为 value 和 Thread
之间存在强引用,并且不会被回收,就可能导致内存泄露
解决方案
那么针对这种情况,我们应该如何去解决呢?
其实jdk已经帮我们做了一些处理, 在调用 set()
remove()
rehash()
这些方法的时候,系统会扫描所有的key为null的 Entry
,把这些对应的 value 都置空,这样value对象就可以被回收,具体是调用的 resize()
这个方法,代码如下
虽然jdk帮我们处理掉一些情况,但是如果 ThreadLocal
用不到了,那基本也不会去调用这几个方法, resize()
也就不会被触发,那么value还是不能被回收的
这种情况下,我们只好在使用完毕之后手动去调用 remove()
方法,避免内存泄露,这也是阿里巴巴开发规范中所规定的
共享对象
第二个要注意的就是 ThreadLocal
中不能存放共享变量,如果放进去的是一个共享变量,比如用 static 修饰的对象,那么 ThreadLocal.get()
获取到的还是这个对象本身,就还是会有线程安全的问题
总结
- 了解
Thread
,ThreadLocal
,ThreadLocalMap
之间的关系
了解常用的几个方法
setInitialValue()
get()
set()
remove()
最后还有使用 ThreadLocal
造成的内存泄漏的原因和解决方法