ThreadLocal
ThreadLocal是什么?
可以给线程设置独有的变量,在线程生命周期内有效。threadLocal提供了线程之间的数据隔离能力,每个线程只能访问到自己的数据。
如何做到数据隔离的?
我们根据一个示例代码来分析一下:
public class Demo {
private static final ThreadLocal<String> FLAG = new ThreadLocal<>();
public static void mark() {
FLAG.set("xxx");
}
}
set方法源码如下:
1,其中根据线程t获取一个ThreadLocalMap的对象
2,调用map.set()方法,key是this(即上面示例的FLAG对象),value为给当前线程设置的值

getMap源码:
即返回线程t的thradLocalls变量


分析到这里已经比较明确了,可以得知ThreadLocal的结构大致如下图所示:

1,ThreadLocal是个工具类,其本身不存储数据,只提供了get,set,remove等方法用来操作数据
2,每个线程里都有个ThreaLocalMap对象,用于存储实际数据,此对象核心字段:
Entry[ ] table :用于存储数据的数组
int size :当前数据项个数(不是table数组长度,table更长,size是实际有数据项的个数)
int Threshold :触发table扩容的阈值
3,Entry是存储数据的类,key是ThreadLocal对象的弱引用,value是当前线程存储的值。

Entry的Key为什么要设计为弱引用?
我们先来思考一个问题,如果key是强引用指向ThreadLocal对象的话,会有什么问题?

- 假设存在某个类C有个实例字段ThreadLocal<String> temp,并且dosomeThing()方法使用了temp字段。
- 并且有个线程池里的线程T1,经常使用类C,并且每次都是new的(多例)。
- 那么经过一段时间之后,在堆内存里必然会有很多个C类的实例,这些实例里的temp字段,都被线程T1里threadLocalMap里的Entry的key强引用。并且这些Entry再也无法被访问到(因为类C每次都是new出来的,每个实例里的temp字段都是不相等的)。
- T1是线程池里的线程,一般会长时间存在,所以temp对象无法被回收掉,C的实例也无法被回收掉,这就发生了非常严重的内存泄漏。
Entry的key被设计为弱引用,主要是用来防止内存泄露的。但是即使是设计成弱引用,如果使用不当,还是存在内存泄露的风险。
内存泄露问题

- Entry的key是弱引用的。GC时,当指向ThreadLocal对象的引用都是弱引用的话,此ThreadLocal对象会被回收掉,指向此ThreadLocal的引用会变为null。
- 如上图所示:C类的实例被回收后,Entry的key变为null,但value会继续存在。因为key为null了,所以此Entry再也无法被访问到。(ThreadLocal的get,set,remove方法都会删除key为null的Entry)
- 如果线程T1一直存活,且也不再访问threadLocalMap,那么这些Entry一直不会释放,即发生了内存泄漏。如果这样的线程很多的话,必然是个灾难。
4,所以用完ThreadLocal后,调用下remove方法,是个好习惯。
把TreadLocal变量定义为常量,也能在一定的程度上避免此问题。
ThreadLocal的好处
实现线程间数据隔离还有其他的方式,那ThreadLocal有什么好处呢?
我认为主要的因素是无锁访问,性能较好。
InheritableThreadLocal
ThreadLocal只能在当前线程中使用。
如果当前线程另起了子线程,想要在子线程中取到父线程中ThreadLocal的数据,则必须使用InheritableThreadLocal。
当前线程在创建子线程时,会将InheritableThreadLocal里的值复制到子线程的InheritableThreadLocal中
(PS: 这意味着子线程创建之后,父线程中InheritableThreadLocal的值发生变化的话,子线程是无法感知到的。)
Thread thread = new Thread();
1,如下所示,创建线程时会调用init方法初始化线程。
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }2,继而调用另一个init重载方法

init方法源码如下所示:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//取到当前线程
Thread parent = currentThread();
//.... 省略部分代码 ....
//parent线程(即当前在执行new Thread()的线程)有inheritableThreadLocal,则将其值复制到new出来的线程里。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}