从TL、ITL到TT

打印 上一主题 下一主题

主题 899|帖子 899|积分 2697

1、概述

ThreadLocal(TL)是Java中一种线程局部变量实现机制,他为每个线程提供一个单独的变量副本,保证多线程场景下,变量的线程安全。经常用于代替参数的显式传递。
InheritableThreadLocal(ITL)是JDK提供的TL增强版,而TransmittableThreadLocal(TTL)是阿里开源的ITL增强版
这些ThreadLocal在不同场景下有不同用途,我们来分析一下:
2、ThreadLocal

ThreadLocal主要的方法有四个:initialValue、set、get、remove
2.1、初始化——initialValule

当线程首次访问该ThreadLocal时(ThreadLocal.get()),会进行初始化赋值。我们常用两种方法初始化ThreadLocal
2.1.1、重写initialValue
  1. ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
  2.     @Override
  3.     protected String initialValue() {
  4.         return "";
  5.     }
  6. };
复制代码
2.1.2、调用ThreadLocal.withInitial
  1. ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
复制代码
他会创建一个SuppliedThreadLocal内部类
  1. public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
  2.     return new SuppliedThreadLocal<>(supplier);
  3. }
复制代码
2.3、取值——get
  1. static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
  2.     private final Supplier<? extends T> supplier;
  3.     SuppliedThreadLocal(Supplier<? extends T> supplier) {
  4.         this.supplier = Objects.requireNonNull(supplier);
  5.     }
  6.     @Override
  7.     protected T initialValue() {
  8.         //当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化
  9.         return supplier.get();
  10.     }
  11. }
复制代码
2.4、清空——remove
  1. public void set(T value) {
  2.     Thread t = Thread.currentThread();
  3.     ThreadLocalMap map = getMap(t);
  4.     if (map != null)
  5.         map.set(this, value);
  6.     else
  7.         createMap(t, value);
  8. }
复制代码
3、InheritableThreadLocal

3.1、TL在父子线程场景下存在的问题

我们先来看一个例子
  1. ThreadLocalMap getMap(Thread t) {
  2.     return t.threadLocals;
  3. }
复制代码
打印结果如下,可见子线程的ThreadLocal是初始值,并没有使用父线程修改后的值:
  1. static class ThreadLocalMap {
  2.     static class Entry extends WeakReference<ThreadLocal<?>> {
  3.         Object value;
  4.         Entry(ThreadLocal<?> k, Object v) {
  5.             //ThreadLocal的引用是“key”
  6.             super(k);
  7.             //线程局部变量是value
  8.             value = v;
  9.         }
  10.     }
  11.     //Entry数组
  12.     //value具体放在哪个index下,是由ThreadLocal的hashCode算出来的
  13.     private Entry[] table;
  14. }
复制代码
线程的ThreadLocalMap是首次访问时创建的,所以子线程使用ThreadLocal的时候,会初始化一个新的ThreadLocal,线程局部变量为默认值
⚠️所以,TL不具有遗传性
3.2、ITL的解决方案

为了解决TL子线程遗传性的问题,JDK引入了ITL
他继承ThreadLocal,重写了childValue、getMap、createMap三个方法
  1. public T get() {
  2.     Thread t = Thread.currentThread();
  3.     //1、获取线程的ThreadLocalMap
  4.     ThreadLocalMap map = getMap(t);
  5.     if (map != null) {
  6.         //2、根据ThreadLocal的hashCode,获取对应Entry下的value
  7.         ThreadLocalMap.Entry e = map.getEntry(this);
  8.         if (e != null) {
  9.             @SuppressWarnings("unchecked")
  10.             T result = (T)e.value;
  11.             return result;
  12.         }
  13.     }
  14.     //3、如果没有赋过值,则初始化
  15.     return setInitialValue();
  16. }
复制代码
这里出现了inheritableThreadLocals,他存储的就是从父线程拷贝过来的ThreadLocal,这个值是在父线程首次修改ThreadLocal的时候赋值的,然后在子线程创建时拷贝过来的
  1. public void remove() {
  2.      ThreadLocalMap m = getMap(Thread.currentThread());
  3.      if (m != null)
  4.          //会将对应Entry、包括他的key、value手动置null
  5.          m.remove(this);
  6. }
复制代码
使用ITL的效果
  1. public static void main(String[] args) throws InterruptedException {
  2.     ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "A");
  3.     threadLocal.set("B");
  4.     Thread thread = new Thread(() -> {
  5.         System.out.println("子线程ThreadLocal:" + threadLocal.get());
  6.     }, "子线程");
  7.     thread.start();
  8.     thread.join();
  9. }
复制代码
打印结果如下,子线程拷贝了父线程ThreadLocal:
  1. 子线程ThreadLocal:A
复制代码
总结一下,ITL解决父子线程遗传性的核心思路是,将可遗传的ThreadLocal放在父线程新的ThreadLocalMap中,在子线程首次使用时进行拷贝
4.、TransmittableThreadLocal

4.1、ITL在线程复用场景下存在的问题

我们再从一个简单的例子说起
  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2.     protected T childValue(T parentValue) {
  3.         return parentValue;
  4.     }
  5.     ThreadLocalMap getMap(Thread t) {
  6.        return t.inheritableThreadLocals;
  7.     }
  8.     void createMap(Thread t, T firstValue) {
  9.         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  10.     }
  11. }
复制代码
打印结果如下,子线程在第二次打印时,并没有拷贝父线程的ThreadLocal,使用的还是首次拷贝的值:
  1. 子线程ThreadLocal:A父线程修改ThreadLocal为C子线程ThreadLocal:A
复制代码
⚠️可复用的子线程不会感知父线程ThreadLocal的变化
4.2、TTL的解决方案

4.2.1、TTL的使用

TTL在ITL上做了稍微复杂的封装,我们从使用开始了解
引入依赖
  1. public static void main(String[] args) throws InterruptedException {
  2.         ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
  3.             @Override
  4.             protected String initialValue() {
  5.                 return "A";
  6.             }
  7.         };
  8.         threadLocal.set("B");
  9.         Thread thread = new Thread(() -> {
  10.             System.out.println("子线程ThreadLocal:" + threadLocal.get());
  11.         }, "子线程");
  12.         thread.start();
  13.         thread.join();
  14. }
复制代码
在使用TTL时,线程需要经过TTL封装,线程池同理
  1. 子线程ThreadLocal:B
复制代码
打印结果如下,子线程每次都会获取父线程的ThreadLocal
  1. 子线程ThreadLocal:A父线程修改ThreadLocal为C子线程ThreadLocal:C子线程修改ThreadLocal为D子线程ThreadLocal:C
复制代码
从使用上看,TTL要求将任务封装,那我们就从ThreadLocal和ExecutorService两部分入手
4.2.2、TTL对ThreadLocal的封装

下面是TTL的取值和赋值逻辑,都涉及一个关键方法addThisToHolder,对应的属性holder会在线程池执行任务时用到
  1. 子线程ThreadLocal:B
  2. 父线程修改ThreadLocal为C
  3. 子线程ThreadLocal:B
复制代码
4.2.3、TTL对任务的封装
  1. <dependency>
  2.     <groupId>com.alibaba</groupId>
  3.     <artifactId>transmittable-thread-local</artifactId>
  4.     <version>latest</version>
  5. </dependency>
复制代码
4.2.3.1、任务构建

TtlRunnable构造方法
这里都是主线程在操作,因为任务是主线程提交的
  1. public static void main(String[] args) throws InterruptedException, ExecutionException {
  2.     ThreadLocal<String> threadLocal = new TransmittableThreadLocal<String>() {
  3.         @Override
  4.         protected String initialValue() {
  5.             return "A";
  6.         }
  7.     };
  8.     threadLocal.set("B");
  9.     ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
  10.     executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
  11.     Thread.sleep(1000);
  12.     threadLocal.set("C");
  13.     System.out.println("父线程修改ThreadLocal为" + threadLocal.get());
  14.     executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
  15.     Thread.sleep(1000);
  16.     executorService.submit(() -> {
  17.         threadLocal.set("D");
  18.         System.out.println("子线程修改ThreadLocal为" + threadLocal.get());
  19.     });
  20.     Thread.sleep(1000);
  21.     executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get()));
  22.     Thread.sleep(1000);
  23. }
复制代码
这里有一个关键属性capturedRef,他是一个原子引用,存了TTL
  1. 子线程ThreadLocal:B
  2. 父线程修改ThreadLocal为C
  3. 子线程ThreadLocal:C
  4. 子线程修改ThreadLocal为D
  5. 子线程ThreadLocal:C
复制代码
4.2.3.2、任务执行

任务执行的代码如下,在任务执行前回放ThreadLocal,在任务执行后恢复ThreadLocal:
这里都是子线程在操作,因为任务都是子线程执行的
  1. //TransmittableThreadLocal.addThisToHolder()
  2. private void addThisToHolder() {
  3.     //InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
  4.     if (!holder.get().containsKey(this)) {
  5.         //holder是静态变量,他会把TTL存到当前线程的map中
  6.         //value是null,他其实是把Map当Set用
  7.         //主线程赋值时,会获取主线程的holderMap,然后把TTL存进去
  8.         holder.get().put((TransmittableThreadLocal<Object>) this, null);
  9.     }
  10. }
  11. @Override
  12. public final void set(T value) {
  13.     if (!disableIgnoreNullValueSemantics && null == value) {
  14.         remove();
  15.     } else {
  16.         super.set(value);
  17.         //当主线程赋值时,会将自己的TTL放到自己的map中
  18.         addThisToHolder();
  19.     }
  20. }
  21. @Override
  22. public final T get() {
  23.     T value = super.get();
  24.     if (disableIgnoreNullValueSemantics || null != value)
  25.         addThisToHolder();
  26.     return value;
  27. }
复制代码
总结一下,TTL让子线程感知父线程变化的核心思路是,主线程在任务提交时构建ThreadLocal副本,在子线程执行任务时供其使用
⚠️提交和执行任务会对TTL进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略
TTL官方性能测试
作者:京东物流 刘朝永
来源:京东云开发者 自猿其说

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美丽的神话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表