前言

TheadLocal主要用于将似有线程和该线程存放的副本对象做一个映射,是的线程之间互不影响。
它为当前执行线程提供了一个副本,用于保存对象。使得线程之间都互不干扰。

结构

image.png
图片来自于参考链接

  • 每个Thread内部都有一个Map
  • Map里面存储线程本地对象(key)和线程的变量副本(value)

    使用

    1. private static class NameHolder {
    2. private static ThreadLocal<String> LOCAL = new ThreadLocal<>();
    3. public static void set(String name) {
    4. LOCAL.set(name);
    5. }
    6. public static String get() {
    7. return LOCAL.get();
    8. }
    9. public static void remove() {
    10. LOCAL.remove();
    11. }
    12. }

    建议:

  1. 尽量采取一个单独的类来保存TheadLocal
  2. 使用public static 进行修饰。

ThreadLocal

TheadLocal 保存来自于Thead对象。

  1. public class Thread implements Runnable {
  2. ThreadLocal.ThreadLocalMap threadLocals = null;
  3. }

TheadLocal核心主要有get、set、remove方法。

  1. public class ThreadLocal<T> {
  2. public T get() {
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null) {
  6. ThreadLocalMap.Entry e = map.getEntry(this);
  7. if (e != null) {
  8. @SuppressWarnings("unchecked")
  9. T result = (T)e.value;
  10. return result;
  11. }
  12. }
  13. return setInitialValue();
  14. }
  15. public void set(T value) {
  16. Thread t = Thread.currentThread();
  17. ThreadLocalMap map = getMap(t);
  18. if (map != null)
  19. map.set(this, value);
  20. else
  21. createMap(t, value);
  22. }
  23. public void remove() {
  24. ThreadLocalMap m = getMap(Thread.currentThread());
  25. if (m != null)
  26. m.remove(this);
  27. }
  28. }

ThreadLocalMap

ThreadLocal的内部类,独立实现了Map的功能。
主要使用Entry来保存K-V结构数据,Entry的key只能是ThreadLocal对象。

  1. static class Entry extends WeakReference<ThreadLocal> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

一个线程如果需要需要存放多个值,定义多个TheadLocal对象即可,最后都会保存在TreadLocalMap当中的。

内存泄露

image.png
说明:
线程运行时,栈中存在当前 Thread 的栈帧,它持有 ThreadLocalMap 的强引用。
ThreadLocal 所在的类持有一个 ThreadLocal 的强引用;同时,ThreadLocalMap 中的 Entry 持有一个 ThreadLocal 的弱引用。

场景一

线程正常执行消亡。那么ThradLocalMap引用不存在了。发生GC时,弱引用也会断开,整个ThreadLocalMap都会被回收。

场景二

如果是线程池的话,如果创建TheadLocal的线程一直持续运行,那么经过GC后,Entry持有的ThreadLocal引用断开,Entry的key为空,value不为空。当前线程一直持有TheadLocalMap,对象不会回收就产生内存泄露了。

变量属性private static

一般我们使用的都用都是定义为 private static 属性,如下:

  1. private static ThreadLocal<String> LOCAL = new ThreadLocal<>();

这里对此此修饰有如下说明:

  1. private 修饰:使用private修饰是普遍问题,就当做是一个普通属性就好了。
  2. static 修饰:这样做有好处,避免了错误产生,可以避免重复创建TSO(Thread Specific Object,即ThreadLocal所关联的对象)所导致的浪费;坏处是这样做可能正好形成内存泄漏所需的条件。

关键在于:static防止了无意义的多实例。
此时也有坏处,由于彩玉static,TheadLcalRef生命周期演成了,ThreadMap的key在线程中一直存在,导致得不到释放,必须手动释放处理。

InheritableThreadLocal

TheadLocal 只能绑定当前线程,如果当前线程的创建了子线程,那么访问不到TheadLocal,这时候就需要使用InheritableThreadLocal 处理了。

  1. public class Thread implements Runnable {
  2. ......(其他源码)
  3. /*
  4. * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
  5. */
  6. ThreadLocal.ThreadLocalMap threadLocals = null;
  7. /*
  8. * InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
  9. * 主要用于父子线程间ThreadLocal变量的传递
  10. * 本文主要讨论的就是这个ThreadLocalMap
  11. */
  12. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  13. ......(其他源码)
  14. }

核心实现

当前线程创建子线程时,会执行init方法,此时,子线程会将parentMap中的所有记录逐一复制到自身线程当中。

TransmittableThreadLocal

TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。 这里解释的比较清楚。主要为了解决线程池之间的传递问题。
GitHub地址:https://github.com/alibaba/transmittable-thread-local

使用

  1. TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>();
  2. context.set("value-set-in-parent");
  3. Runnable task = new RunnableTask();
  4. // 额外的处理,生成修饰了的对象ttlRunnable
  5. Runnable ttlRunnable = TtlRunnable.get(task);
  6. executorService.submit(ttlRunnable);
  7. // =====================================================
  8. // Task中可以读取,值是"value-set-in-parent"
  9. String value = context.get();

核心实现

主要就是引入了holder变量,再定义TransmittableThreadLocal时,同时初始了holder。TtlRunnable包裹Runnable时,会将此值带过去。

参考