https://zhuanlan.zhihu.com/p/20213204
http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/
This class provides thread-local variables. These variables differ fromtheir normal counterparts in that each thread that accesses one (via itsgetorsetmethod) has its own, independently initializedcopy of the variable.ThreadLocalinstances are typically privatestatic fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
ThreadLocal类用来提供线程的局部变量。
这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。
ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
总结:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
思考:其实目的很简单,就是要将这个变量和线程绑定起来,通过如线程id等唯一标识就能获取到这个变量。如果是自己设计的话,会怎么样设计?最简单的一种思路应该是:ThreadLoacl,提供一个类似map的成员变量,key - threadId, value是一个键值对(map或entry):key -objKey, value -objValue。然后通过线程id和objKey,获取真正的value,so easy~!是的,好像早期jdk就是类似这样实现的?
继续思考:当线程数量增多时,ThreadLocal将会变得非常庞大。于是想着将这些变量交给Thread各个线程自己来存储,这是个好主意,Thread中增加一个map,key -objKey, value -objValue。
事实也确实是如此,后面的版本里,Thread类增加了一个成员变量threadLocals,ThreadLocal.ThreadLocalMap threadLocals 这就更加有趣了,这里先不管ThreadLocal和ThreadLocalMap的关系。我们暂且知道threadLocals 是一个map,就是存key - value的。稍微看下ThreadLocalMap,会发现里面存的是个Entry,key是ThreadLocal的WeakReference弱引用,先不管什么是弱引用,暂时认为就是存的ThreadLocal自身,value就是真正的值了。ThreadLocalrequestId = new ThreadLocal(); requestId.set(id),requestId.get(),用法简单
也就是说,set之后threadLocals会加一条记录:key -requestId, value -id,每一个ThreadLocal只对应一个局部变量,如果另外还有其他局部变量,需要重新new一个。
这时候再来看一下,官方以下代码中的推荐用法,set和remove方法是额外加的。
public class ThreadIdHolder { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocalthreadId = new ThreadLocal() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void set(int id) {
threadId.set(id);
}
public static void remove() {
threadId.remove();
}
}
以上用法通过一个外部类Holder来作为threadId变量的封装再看看官方说明:ThreadLocal instances are typically private static fields in classes希望是private static的字段,然后通过外部的static方法来提供访问或修改,为何推荐是private static的呢?ThreadLocalthreadId = new ThreadLocal()
1、当多个线程都持有个变量时,threadLocalMap中的key都是threadId,value是真正的值
所以,threadId应该是个公共的,不属于具体某一个线程的,故static,否则还要将threadId传递给各个线程
private的原因应该是不希望直接操作threadId了,而是通过封装来提供适当的操作权限。
2、就当只有一个线程,在不同的上下文中,传递threadId也比较麻烦,所以推荐static
所以ThreadLocal的使用场景,考虑以下:
1、当多个线程都有该局部变量,但各个线程的值需要互相隔离
2、单个线程中,在不同上下文中,经常要使用到某个变量时
当然如果不按推荐使用,例如,直接在某一个类中ThreadLocalthreadId = new ThreadLocal();
那么,这样的结果就是由具体某一个线程来完成实例化,然后传递给各个线程。这时候就特别要注意其生命周期,threadId是否会被回收。
由于,ThreadLocalMap的entry持有的是ThreadLocal的弱引用,如果没有强引用的情况下即被回收了,entry将引用不到ThreadLocal,
也就是说
继续上面的问题,entry引用不到ThreadLocal,就等于ThreadLocalMap中存了一些过期的变量,容易引起内存泄露。
于是,ThreadLocalMap在设计的时候也想到了,增加了一些保护措施,在其get/set等方法中会判断并清空这些key为空的Entry
但是最好的习惯还是用完就remove掉。