当前位置: 首页 > article >正文

ThreadLocal线程局部变量

ThreadLocal

  • 一、介绍
  • 二、使用场景
  • 三、原理分析
    • 3.0 Thread
    • 3.1 ThreadLocal
    • 3.2 inheritableThreadLocals
    • 3.3 TransmittableThreadLocal
  • 四、Transmittable使用
    • 4.1 修饰Runnable和Callable任务
    • 4.2 修饰线程池
    • 4.3 使用Java Agent来修饰JDK线程池实现类
  • 五、注意事项

如有侵权,无心侵权,请联系~
如有错误,也欢迎批评指正~

一、介绍

ThreadLocal是一个关于创建线程局部变量的类,翻译成中文比较准确的叫法应该是:线程局部变量;是一个将在多线程中为每一个线程创建单独的变量副本的类,为每个线程创建单独的变量副本时,能够避免因多线程操作共享变量而导致的数据不一致的情况。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

二、使用场景

  • 每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)

  • 每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

  • 代替参数的显式传递;

  • 全局存储用户信息;

  • 解决线程安全问题,如SimpleDateFormat;

三、原理分析

以下是ThreadLocal三个比较重要的类,接下来进行一一讲解。

ThreadLocal --子类–> InheritableThreadLocal --子类–> TransmittableThreadLocal

3.0 Thread

在看这个三类之前,先看下Thread这个类,因为这三个类在每个线程中对应一个ThreadLocalMap属性

public
class Thread implements Runnable {
	private volatile String name;
    private int priority;
  // 总之ThreadLocal变量存放在线程的ThreadLocalMap属性中, InheritableThreadLocal存放在inheritableThreadLocals中
  /* ThreadLocal values pertaining(附属) to this thread. This map is maintained(维持)
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;  // ThreadLocal对应的线程的ThreadLocalMap就是该属性,ThreadLocalMap是懒加载,当获取该属性的时候,
  // 但是还没创建的时候,ThreadLocal就会创建ThreadLocalMap ThreadLocal的set() get()都会调用createMap()。针对主线程所有的ThreadLocal变量存储在这个里面

  /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // InheritableThreadLocal对应的线程的ThreadLocalMap就是该属性,该属性和上述不同,该属性会在Thread线程
  // 启动创建的时候就进行创建,并且会把父线程的inheritableThreadLocals复制过来。针对主线程所有的ThreadLocal变量存储在这个里面

 	public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
	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();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        // Thread线程初始化方法,创建inheritableThreadLocals.
        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();
    }
}

3.1 ThreadLocal

public class ThreadLocal<T> {
  // 每个线程的ThreadLocalMap的key(referent)就是Thread的引用,而ThreadLocalMap底层通过Entry数组(table)进行存储,
  // Entry对象有两个属性:referent(这是个弱饮用,指向ThreadLocal变量)和value
  private final int threadLocalHashCode = nextHashCode();  // 该变量就是为了确定这个threadLocal对象放在entry数组的第几个 
  //(i = t.threadLocalHashCode & (table.leng-1))如果hash冲突后挨着往后找 直到为null
  
  // 当线程想要获取获取threadLocal值的时候,如果此时ThreadLocalMap中没有或者是ThreadLocalMap还没创建(懒加载)就会调用该方法为其赋值。ThreadLocal类默认是为null(个人理解,
  // 完全可以自己实现一个类,继承ThreadLocal重写initialValue()修改默认值)
  protected T initialValue() {
        return null;
  }
  
  // 获取该线程的ThreadLocal值
   public T get() {
     Thread t = Thread.currentThread();
     // 获取该线程的ThreadLocalMap,t.threadLocals
     ThreadLocalMap map = getMap(t);
     if (map != null) {
       ThreadLocalMap.Entry e = map.getEntry(this); // 这里从ThreadLocalMap的table数组中获取entry的时候会进行内存清理(key=null的value也值为null,
       // 但是不会像remove()方法那样挨个清除,因为这个方法主要是找到这个threadLocal的entry,在寻找的时候顺便进行清除)
       if (e != null) {
         // 如果线程的ThreadLocalMap中存在key为该threadLocal的entry,直接返回value结果即可
         @SuppressWarnings("unchecked")
         T result = (T)e.value;
         return result;
       }
     }
     // 如果ThreadLocalMap还未创建或者table中不存在key为该threadLocal的entry,进行初始化
     return setInitialValue();
  }
  
  // 设置初始值
  private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      map.set(this, value);  //通过上述的initialValue()可以看出ThreadLocal的初始化默认值为null
    } else {
      createMap(t, value);  //线程的ThreadLocalMap不存在,则直接创建t.threadLocals = new ThreadLocalMap(this, firstValue);创建table并赋值
    }
    if (this instanceof TerminatingThreadLocal) {
      TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
  }
  
  ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
  }
  
  // 将该threadLocal从当前线程的ThreadLocalMap中移除
  public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
      m.remove(this);
    }
  }
  
  // 将该threadLocal存储到当前线程的threadLocalMap中,其实对map的操作都是对table的操作
  public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      map.set(this, value); // 和get()方法一样也会清理垃圾 防止内存泄漏 但不会挨个清除
    } else {
      createMap(t, value);
    }
  }
}

// ThreadLocal的静态内部类
static class ThreadLocalMap {
  
  // ThreadLocalMap内部还有内部类,WeakReference基类有个属性就是referent
  static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
      super(k);
      value = v;
    }
  }
  
  // 这些参数都是为table数组使用,和Map一样,数组初始大小、数组元素个数,扩容阈值等
   private static final int INITIAL_CAPACITY = 16;

  /**
  	* The table, resized as necessary.
    * table.length MUST always be a power of two.
  */
  private Entry[] table;

  /**
  	* The number of entries in the table.
  */
  private int size = 0;

  /**
 	 * The next size value at which to resize.
  */
  private int threshold; // Default to 0

  /**
  	* Set the resize threshold to maintain at worst a 2/3 load factor.
  */
  private void setThreshold(int len) {
    threshold = len * 2 / 3;
  }
  
  // 从table数组中删除相应的ThreadLocal
  private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
      // 减少内存泄漏
      if (e.get() == key) {
        e.clear();  // this.referent = null; 将对应的key值为null
        expungeStaleEntry(i);  // 1)会将entry的value值为null 等下次gc的时候就收掉value对象 2)检查table中其他的entry是不是key为null(内存泄漏),以及重新hash
        return;
      }
    }
  }
}

内存泄漏:
实线代表强引用,虚线代表弱引用
在这里插入图片描述
有上述源码可知,每个thread中都存在一个ThreadLocal.ThreadLocalMap, Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。但是,value却不能回收,因为存在一条从current thread连接过来的强引用。只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收
所以只要这个线程对象被gc回收,就不会出现内存泄露

而线程池是复用线程,不会回收Thread,线程池操作ThreadLocal,就会造成内存泄露, 因为对于线程池里面不会销毁的线程, ThreadLocal.ThreadLocalMap中总会存在着<ThreadLocal, Object>的强引用, 导致这部分空间不会释放,造成内存泄漏
所以为了杜绝内存泄漏,每次使用之后都要remove

虽然 set(),get() 方法中,都有清除无效 Entry 的操作,set()方法中调用了replaceStaleEntry,get()方法中调用了expungeStaleEntry方法,确保将key为null的value也置为null,这样做是为了降低内存泄漏发生的可能,但是没有remove()方法清理的彻底,所以建议还是如果不使用了使用remove()进行及时回收。

3.2 inheritableThreadLocals

通过上面Thread的讲解可以知道为什么这个会继承父线程的值了,因为父线程的ThreadLocal都存放在inheritableThreadLocals变量,而这个变量就在线程创建的时候就创建并且把父线程的该属性赋值过来(并不是直接复制,而是一个个遍历创建赋值)

private ThreadLocalMap(ThreadLocalMap parentMap) {
  Entry[] parentTable = parentMap.table;
  int len = parentTable.length;
  setThreshold(len);
  table = new Entry[len];

  for (int j = 0; j < len; j++) {
    Entry e = parentTable[j];
    if (e != null) {
      @SuppressWarnings("unchecked")
      ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
      if (key != null) {
        Object value = key.childValue(e.value);
        Entry c = new Entry(key, value);
        int h = key.threadLocalHashCode & (len - 1);
        while (table[h] != null)
          h = nextIndex(h, len);
        table[h] = c;
        size++;
      }
    }
  }
}

正是这个属性是在线程创建的时候就创建了,导致只有线程创建的第一次才会和主线程保持一致。但是我们平时一般不直接new线程,而是用线程池,线程池中的线程是复用的,这就导致第一次创建线程的时候可以和主线程保持一致,但是这个线程用完被放回线程池,别的线程想用的时候不会和主线程保持一致了。

3.3 TransmittableThreadLocal

阿里开源组件,继承自InheritableThreadLocal,用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable 使用.

TransmittableThreadLocal是在任务创建的时候进行进行创建Map,而不是线程启动的时候。TransmittableThreadLocal的map是和任务绑定的,而inheritableThreadLocals的map是和thread线程绑定的。
在这里插入图片描述
holder是静态变量,属于类,所有的线程都可以看到。这就导致创建多个ThreadLocal都存放在holder里面。holder里面存储了所有的TransmittableThreadLocal对象。

holder其实存放放的一直都是TransmittableThreadLocal变量,value都是null。在创建TtlRunnable的时候,会遍历holder,然后读取此时的所有TransmittableThreadLocal数据快照。在执行ttl任务之前会被此时的TransmittableThreadLocal和其他的ThreadLocal变量都备份好,并将所有的TransmittableThreadLocal设置成创建TtlRunnable任务时候的得到的快照。得到任务执行还,所有的TransmittableThreadLocal和其他的ThreadLocal变量恢复到备份的数据。
具体看下源码,以TtlCallable为例子【TtlRunnable同理】:
这个类主要是做了三件事:

  • 在创建任务的时候,创建此时当前线程所有的TransmittableThreadLocal对象【快照1】
  • 在任务执行的过程中:
    • 保存此时的所有TransmittableThreadLocal对象【快照2】,并且回放创建时候的快照【快照1】
    • 执行任务
    • 回放任务执行之前的TransmittableThreadLocal对象【快照2】
public final class TtlCallable<V> implements Callable<V>, TtlWrapper<Callable<V>>, TtlEnhanced, TtlAttachments {
    private final AtomicReference<Object> capturedRef;
    // 这个就是咱们真实的任务,TtlCallable封装了Callable
    //  1、TransmittableThreadLocal线程局部变量【准确说应该是任务局部变量】
    //  2、注册的ThreadLocal,也就是虽然是个线程局部变量,但是也期望是任务局部变量
    private final Callable<V> callable;
    private final boolean releaseTtlValueReferenceAfterCall;

    private TtlCallable(@NonNull Callable<V> callable, boolean releaseTtlValueReferenceAfterCall) {
    	// 这个对象会存放两部分对象的快照【上述的快照1】:
    	//	1、保存所有的TransmittableThreadLocal对象
    	//	2、保存注册的ThreadLocal对象,虽然是ThreadLocal对象,但是也想变成任务局部变量
        this.capturedRef = new AtomicReference<Object>(capture());
        this.callable = callable;
        this.releaseTtlValueReferenceAfterCall = releaseTtlValueReferenceAfterCall;
    }
    @Override
    public V call() throws Exception {
      Object captured = capturedRef.get();
       if (captured == null || releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) {
           throw new IllegalStateException("TTL value reference is released after call!");
       }
	   // 保存此时的所有TransmittableThreadLocal对象【快照2】,并且回放创建时候的快照【快照1】
       Object backup = replay(captured);
       try {
       	   // 执行任务
           return callable.call();
       } finally {
       	   // 回放任务执行之前的TransmittableThreadLocal对象【快照2】
           restore(backup);
       }
   }
}

Transmitter类主要就是完成上述说的保存、回放快照:

public static class Transmitter {
   /**
     * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread.
     */
    @NonNull
    public static Object capture() {
        return new Snapshot(captureTtlValues(), captureThreadLocalValues());
    }

    private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
        HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
            ttl2Value.put(threadLocal, threadLocal.copyValue());
        }
        return ttl2Value;
    }

    private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
        for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            final TtlCopier<Object> copier = entry.getValue();

            threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
        }
        return threadLocal2Value;
    }
    @NonNull
    public static Object replay(@NonNull Object captured) {
        final Snapshot capturedSnapshot = (Snapshot) captured;
        return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
    }

    @NonNull
    private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
        HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();

        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();

            // backup
            backup.put(threadLocal, threadLocal.get());

            // clear the TTL values that is not in captured
            // avoid the extra TTL values after replay when run task
            if (!captured.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }

        // set TTL values to captured
        setTtlValuesTo(captured);

        // call beforeExecute callback
        doExecuteCallback(true);

        return backup;
    }

    private static HashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> captured) {
        final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>();

        for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            backup.put(threadLocal, threadLocal.get());

            final Object value = entry.getValue();
            if (value == threadLocalClearMark) threadLocal.remove();
            else threadLocal.set(value);
        }

        return backup;
    }
    public static void restore(@NonNull Object backup) {
       final Snapshot backupSnapshot = (Snapshot) backup;
        restoreTtlValues(backupSnapshot.ttl2Value);
        restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
    }

    private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
        // call afterExecute callback
        doExecuteCallback(false);

        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();

            // clear the TTL values that is not in backup
            // avoid the extra TTL values after restore
            if (!backup.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }

        // restore TTL values
        setTtlValuesTo(backup);
    }

    private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
        for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
            TransmittableThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }

    private static void restoreThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> backup) {
        for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }
}

四、Transmittable使用

4.1 修饰Runnable和Callable任务

修饰Runnable

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
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();

修饰Callable

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Callable call = new CallableTask();
// 额外的处理,生成修饰了的对象ttlCallable
Callable ttlCallable = TtlCallable.get(call);
executorService.submit(ttlCallable);

// =====================================================

// Call中可以读取,值是"value-set-in-parent"
String value = context.get();

4.2 修饰线程池

修饰线程池其实底层也是修饰的任务,在线程池执行任务之前创建ttlRunnable

ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);

// =====================================================

// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();

4.3 使用Java Agent来修饰JDK线程池实现类

何为Java Agent以及简单使用

五、注意事项

  • 针对于InheritableThreadLocal,父子线程的inheritableThreadLocals的key是对ThreadLocal变量的弱引用,但是value确实强引用指向对象。如果value是map,一个线程删除map中的key,其他线程也能感知到。最好对ThreadLocal的childValue()或者是TransmittableThreadLocal的copyValue()进行重写,进行深拷贝
    在这里插入图片描述
  • ThreadLocal使用完及时删除,防止出现内存泄漏以及数据紊乱

http://www.kler.cn/news/356176.html

相关文章:

  • 《柬埔寨语翻译通》App是如何实现高棉语语音识别翻译技术的,高精度OCR文字识别技术分享!
  • IO多路复用:select、poll、epoll的底层区别
  • 003 Qt_信号和槽-上
  • FPGA图像处理之均值滤波
  • react子应用嵌入qiankun微前端后,多层抽屉drawer getContainer={false}挂载在当前位置后抽屉不在停靠在窗口的最边上
  • HarmonyOS NEXT开发之ArkTS自定义组件学习笔记
  • 全桥LLC谐振变换器概述及MATLAB仿真
  • LeetCode刷题日记之贪心算法(二)
  • 【汇编语言】寄存器(内存访问)(七)—— CPU提供的栈机制
  • python从0快速上手(十二)高级特性2
  • 如何接受Date范围的数据
  • Chrome DevTools 三: Performance 性能面板扩展—— 性能优化
  • Python | basemap空间绘图 | cartopy | geoviews
  • ABAP:创建/修改客户的银行信息
  • 深度学习模型训练的主要流程(不定时更新中)
  • javaScript逆向怎样做
  • 【赵渝强老师】Oracle的联机重做日志文件与数据写入过程
  • 使用正则解决SQL注入问题
  • 对于六上前二单元的一些感想
  • [Hbase]一 HBase基础