前言
TheadLocal
主要用于将似有线程和该线程存放的副本对象做一个映射,是的线程之间互不影响。
它为当前执行线程提供了一个副本,用于保存对象。使得线程之间都互不干扰。
结构
- 每个Thread内部都有一个Map
Map里面存储线程本地对象(key)和线程的变量副本(value)
使用
private static class NameHolder {
private static ThreadLocal<String> LOCAL = new ThreadLocal<>();
public static void set(String name) {
LOCAL.set(name);
}
public static String get() {
return LOCAL.get();
}
public static void remove() {
LOCAL.remove();
}
}
建议:
- 尽量采取一个单独的类来保存
TheadLocal
。 - 使用
public static
进行修饰。
ThreadLocal
TheadLocal 保存来自于Thead对象。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
TheadLocal核心主要有get、set、remove方法。
public class ThreadLocal<T> {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
}
ThreadLocalMap
ThreadLocal的内部类,独立实现了Map的功能。
主要使用Entry来保存K-V结构数据,Entry的key只能是ThreadLocal对象。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
一个线程如果需要需要存放多个值,定义多个TheadLocal对象即可,最后都会保存在TreadLocalMap当中的。
内存泄露
说明:
线程运行时,栈中存在当前 Thread
的栈帧,它持有 ThreadLocalMap 的强引用。ThreadLocal
所在的类持有一个 ThreadLocal
的强引用;同时,ThreadLocalMap 中的 Entry 持有一个 ThreadLocal
的弱引用。
场景一
线程正常执行消亡。那么ThradLocalMap引用不存在了。发生GC时,弱引用也会断开,整个ThreadLocalMap都会被回收。
场景二
如果是线程池的话,如果创建TheadLocal的线程一直持续运行,那么经过GC后,Entry持有的ThreadLocal引用断开,Entry的key为空,value不为空。当前线程一直持有TheadLocalMap,对象不会回收就产生内存泄露了。
变量属性private static
一般我们使用的都用都是定义为 private static 属性,如下:
private static ThreadLocal<String> LOCAL = new ThreadLocal<>();
这里对此此修饰有如下说明:
private
修饰:使用private修饰是普遍问题,就当做是一个普通属性就好了。static
修饰:这样做有好处,避免了错误产生,可以避免重复创建TSO(Thread Specific Object,即ThreadLocal所关联的对象)所导致的浪费;坏处是这样做可能正好形成内存泄漏所需的条件。
关键在于:static防止了无意义的多实例。
此时也有坏处,由于彩玉static,TheadLcalRef生命周期演成了,ThreadMap的key在线程中一直存在,导致得不到释放,必须手动释放处理。
InheritableThreadLocal
TheadLocal 只能绑定当前线程,如果当前线程的创建了子线程,那么访问不到TheadLocal,这时候就需要使用InheritableThreadLocal
处理了。
public class Thread implements Runnable {
......(其他源码)
/*
* 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
* 主要用于父子线程间ThreadLocal变量的传递
* 本文主要讨论的就是这个ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......(其他源码)
}
核心实现
当前线程创建子线程时,会执行init方法,此时,子线程会将parentMap中的所有记录逐一复制到自身线程当中。
TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。 这里解释的比较清楚。主要为了解决线程池之间的传递问题。
GitHub地址:https://github.com/alibaba/transmittable-thread-local
使用
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>();
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
// 额外的处理,生成修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);
// =====================================================
// Task中可以读取,值是"value-set-in-parent"
String value = context.get();
核心实现
主要就是引入了holder变量,再定义TransmittableThreadLocal
时,同时初始了holder。TtlRunnable包裹Runnable时,会将此值带过去。