ThreadLocal 基本使用
ThreadLocal类提供了线程局部变量,我们可以通过get、set方法来保存、访问只有当前线程才能操作的变量
实现原理
从他的ThreadLocal.set(T value)中看实现
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//不为空 新增
map.set(this, value);
else
//为空 初始化
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
每个线程维护一个ThreadLocalMap对象,key对应的是当前ThreadLocal、value则是你想要存放的数据,这样 当你想要取出指定ThreadLocal数据的时候,只需要从ThreadLocalMap 中去拿出指定ThreadLocal的值,看实现:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过当前ThreadLocal来拿值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//没有初始化的话,执行初始化,返回null
return setInitialValue();
}
ThreadLocalMap
那么ThreadLocalMap具体怎么保存(ThreadLocal->Value)数据的呢
static class ThreadLocalMap {
//内部维护一个Entry数组
private Entry[] table;
//弱引用会在下面说
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化Entry数组
table = new Entry[INITIAL_CAPACITY];
//通过ThreadLocal计算数组下表
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置阈值 INITIAL_CAPACITY(16)*2/3
setThreshold(INITIAL_CAPACITY);
}
...
}
类似HashMap的实现,简易很多,大致是内部维护一个Entry[], 通过ThreadLocal可以计算出唯一下标,给指定下标的Entry赋值,获取的时候计算出下标就可以直接拿到值了
内存泄漏问题
上面看Entry存储的时候ThreadLocal会以弱引用的形式存在,当把ThreadLocal置为空,垃圾回收时候会将ThreadLocal原内存块回收掉,这样就会有个key为空 value有值的Entry。
这个Entry 还有当前线程在持有,所以不会回收掉。一般情况下,随着线程消亡,这个value也会被清除掉。使用线程池的情况下,同一线程一直被重复使用,每次使用过后都会出现一条key为空 value有值的Entry,这样就会发生内存泄漏。
但是翻源码发现,ThreadLocalMap在set、get 都会执行expungeStaleEntry()方法清理过期的数据,这样除非极端情况。你这个线程不在运行了,或者不在执行ThreadLocal逻辑了,否则并不会出现内存泄漏的情况
InheritableThreadLocal
ThreadLocal是为了绑定当前线程,如果希望当前线程的ThreadLocal能被子线程使用,用户就需要在自己代码中实现传递,因此InheritableThreadLocal 就产生了
实现:
Thread类中
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
inheritableThreadLocals变量就是为了可自动向子线程传递的ThreadLocalMap, 有了这个map之后,只需要在父线程创建子线程的时候,将父线程的inheritableThreadLocals拿到,赋值给子线程的inheritableThreadLocals就可以实现传递了
源码如下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
......
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
从上述两个ThreadLocal看,是通过Thread配合实现的
TransmittableThreadLocal
InheritableThreadLocal是在new Thread()的时候,实现继承inheritableThreadLocals的,这种情况下,如果遇到线程池,线程循环使用,继承就不在可用了,下面看个InheritableThreadLocal使用失效的例子
static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(1, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>());
new Thread(() -> {
inheritableThreadLocal.set("Thread-1");
executorService.execute(() -> {
System.out.println("super-thread-1 current:" + Thread.currentThread().getName() + " value:" + inheritableThreadLocal.get());
});
}, "super-thread-1").start();
Thread.sleep(1000);
new Thread(() -> {
inheritableThreadLocal.set("Thread-2");
executorService.execute(() -> {
System.out.println("super-thread-2 current:" + Thread.currentThread().getName() + " value:" + inheritableThreadLocal.get());
});
}, "Thread-2").start();
}
super-thread-1 current:pool-1-thread-1 value:Thread-1
super-thread-2 current:pool-1-thread-1 value:Thread-1
再此背景下TransmittableThreadLocal就产生了,他有几种模式来实现父子线程关系的传递
- 修饰Runnable和Callable
- 修饰线程池
- 使用Java Agent来修饰JDK线程池实现类
它是将 Runnable和Callable装饰了一下,在创建Runnable或Callable的时候(创建Runnable或Callable时绝对是在父线程内),获取父线程需要传递的值,并复制一份到内存中,在执行run()/call()的时候,replay父线程的值,等执行完成之后 在restore为之前的值