让某个需要用到的对象在线程间隔离,每个线程有自己独立的对象
两大场景
1、每个线程需要一个独享的对象(通常是工具类,比如
SimpleDateFormat、Random等线程不安全的工具类)
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
private static final ThreadLocal<SimpleDateFormat> formatter
= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
每个Thread内有自己的副本,不共享。
2、每个线程需要保存全局产量(例如拦截器拦截的用户信息),可以让不同的方法直接使用,避免参数传递的麻烦
用TheadLocal保存一些业务内容(用户权限信息,用户名,userId等信息)
这些信息在同一个线程内相同,不同线程之间不同
强调的是同一个请求(同一个线程内),不同方法之间的共享
不需要重写initialValue() 方法,但是必须手动调用set方法
private static ThreadLocal<User> holder= new ThreadLocal<User>()
每个Thread 对象都持有一个ThreadLocalMap对象、
一个ThreadLocalMap里有多个ThreadLocal对象
ThreadLocalMap
ThreadLocalMap类,就是Thread.threadLocals
ThreadLocalMap 的一个最重要的是一个数据Entry[] table,可以认为是一个map
key:这个ThreadLocal
value:实际需要的变量值
initalValue()
1、该方法返回当前线程对象的初始值,这是一个延迟加载的方法,只有在调用get的时候才会触发
2、如果在get之前使用了set方法,将不会再触发initialValue方法
3、通常,每个线程最多调用一次initialValue方法,但是如果调用了remove方法后,再调用get方法,则会再次调用此方法
4、如果不重写该方法,该方法会返回null,需要我们自己重写该方法
get()
get方法首先取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal引用作为参数传递,取出map中属于本TheadLocal的value
这个map以及map中的key value都是保存在线程中的,而不是ThreadLocal中,因为map是thread持有
remove()
能够删除当前线程对应的ThreadLocal的值
内存泄漏问题
C c = new C(b);
b = null;
考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?答案是否定的,因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,所以GC不会回收b原先所分配的空间!既不能回收利用,又不能使用,这就造成了内存泄露。
那么如何处理呢?
可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)
ThreadLocalMap
首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。
key是弱引用
value是强引用
1、正常情况下线程结束,里面所有东西都会被回收
2、如果线程始终不终止(线程池),value强引用无法被回收,
jdk已经考虑到这个问题,在set remove,rehash方法中会扫描所有key为null的Entry,并吧对象value置为null,这样value会被回收
但是实际中,我们忘记调用这些方法
3、如果一个ThreadLocal不被使用了,那么实际中set remove,rehash方法也不会被调用了,如果线程又不会被终止,那么就会导致value的内存泄漏 (threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露)
我们使用完之后(比如调用链的最后一端,很清楚不会再使用了)应该手动进行删除调用remove方法清除当前ThreadLocal对象
个人理解:正常情况下ThreadLocal对象如果一直使用就不会出现内存泄漏,因为ThreadLocalMap的key是当前ThreadLocal对象,当前对象没有被回收,value就还有用,不算内存泄漏
当一个不再使用的ThreadLocal,那么key可能就被回收了,导致value的泄漏
不应该在ThreadLocal中放静态共享对象
网友评论