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
1ThreadLocal<String> threadLocal = new ThreadLocal<String>() { 2 @Override 3 protected String initialValue() { 4 return ""; 5 } 6};
2.1.2、调用ThreadLocal.withInitial
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
他会创建一个SuppliedThreadLocal内部类
1public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { 2 return new SuppliedThreadLocal<>(supplier); 3}
该类重写了initialValue方法
1static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { 2 3 private final Supplier<? extends T> supplier; 4 5 SuppliedThreadLocal(Supplier<? extends T> supplier) { 6 this.supplier = Objects.requireNonNull(supplier); 7 } 8 9 @Override 10 protected T initialValue() { 11 //当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化 12 return supplier.get(); 13 } 14}
⚠️ITL并没有重新实现withInitial,如果使用withInitial则会创建STL,失去自己增强的特性
2.2、赋值——set
1public 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}
这里出现了一个关键属性ThreadLocalMap,类定义在ThreadLocal中,是Thread的成员变量
1ThreadLocalMap getMap(Thread t) { 2 return t.threadLocals; 3}
ThreadLocalMap内部还有一个内部类Entry,是存值的地方
1static 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}
2.3、取值——get
1public 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}
2.4、清空——remove
1 public void remove() { 2 ThreadLocalMap m = getMap(Thread.currentThread()); 3 if (m != null) 4 //会将对应Entry、包括他的key、value手动置null 5 m.remove(this); 6 }
3、InheritableThreadLocal
3.1、TL在父子线程场景下存在的问题
我们先来看一个例子
1public 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是初始值,并没有使用父线程修改后的值:
子线程ThreadLocal:A
线程的ThreadLocalMap是首次访问时创建的,所以子线程使用ThreadLocal的时候,会初始化一个新的ThreadLocal,线程局部变量为默认值
⚠️所以,TL不具有遗传性
3.2、ITL的解决方案
为了解决TL子线程遗传性的问题,JDK引入了ITL
他继承ThreadLocal,重写了childValue、getMap、createMap三个方法
1public class InheritableThreadLocal<T> extends ThreadLocal<T> { 2 3 protected T childValue(T parentValue) { 4 return parentValue; 5 } 6 7 ThreadLocalMap getMap(Thread t) { 8 return t.inheritableThreadLocals; 9 } 10 11 void createMap(Thread t, T firstValue) { 12 t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); 13 } 14}
这里出现了inheritableThreadLocals,他存储的就是从父线程拷贝过来的ThreadLocal,这个值是在父线程首次修改ThreadLocal的时候赋值的,然后在子线程创建时拷贝过来的
1//父线程部分: 2public void set(T value) { 3 Thread t = Thread.currentThread(); 4 //该方法被ITL重写,访问inheritableThreadLocals为null 5 ThreadLocalMap map = getMap(t); 6 if (map != null) 7 map.set(this, value); 8 else 9 //该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals 10 createMap(t, value); 11} 12 13//子线程部分: 14public Thread(Runnable target) { 15 init(null, target, "Thread-" + nextThreadNum(), 0); 16} 17 18private void init(ThreadGroup g, Runnable target, String name, 19 long stackSize, AccessControlContext acc, 20 boolean inheritThreadLocals) { 21 //省略一些代码... 22 23 //获取当前线程(父线程、也就是创建子线程的线程) 24 Thread parent = currentThread(); 25 //1、允许ThreadLocal遗传(这个默认为true) 26 //2、inheritableThreadLocals不为空,因为父线程调用set了 27 //父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支 28 if (inheritThreadLocals && parent.inheritableThreadLocals != null) 29 this.inheritableThreadLocals = 30 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 31} 32 33//createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝 34private ThreadLocalMap(ThreadLocalMap parentMap) { 35 Entry[] parentTable = parentMap.table; 36 int len = parentTable.length; 37 setThreshold(len); 38 table = new Entry[len]; 39 //深拷贝父线程ThreadLocalMap 40 for (int j = 0; j < len; j++) { 41 Entry e = parentTable[j]; 42 if (e != null) { 43 @SuppressWarnings("unchecked") 44 ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); 45 if (key != null) { 46 //childValue被ITL重写,返回父线程ThreadLocal的值 47 Object value = key.childValue(e.value); 48 Entry c = new Entry(key, value); 49 int h = key.threadLocalHashCode & (len - 1); 50 while (table[h] != null) 51 h = nextIndex(h, len); 52 table[h] = c; 53 size++; 54 } 55 } 56 } 57}
使用ITL的效果
1public 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 14 thread.join(); 15}
打印结果如下,子线程拷贝了父线程ThreadLocal:
子线程ThreadLocal:B
总结一下,ITL解决父子线程遗传性的核心思路是,将可遗传的ThreadLocal放在父线程新的ThreadLocalMap中,在子线程首次使用时进行拷贝
4.、TransmittableThreadLocal
4.1、ITL在线程复用场景下存在的问题
我们再从一个简单的例子说起
1public static void main(String[] args) throws InterruptedException, ExecutionException { 2 ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() { 3 @Override 4 protected String initialValue() { 5 return "A"; 6 } 7 }; 8 threadLocal.set("B"); 9 ExecutorService executorService = Executors.newFixedThreadPool(1); 10 //1、子线程第一次获取ThreadLocal 11 executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get(); 12 Thread.sleep(1000); 13 //2、父线程修改ThreadLocal 14 threadLocal.set("C"); 15 System.out.println("父线程修改ThreadLocal为"+threadLocal.get()); 16 //3、子线程第二次获取ThreadLocal 17 executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get(); 18}
打印结果如下,子线程在第二次打印时,并没有拷贝父线程的ThreadLocal,使用的还是首次拷贝的值:
1子线程ThreadLocal:B 2父线程修改ThreadLocal为C 3子线程ThreadLocal:B
⚠️可复用的子线程不会感知父线程ThreadLocal的变化
4.2、TTL的解决方案
4.2.1、TTL的使用
TTL在ITL上做了稍微复杂的封装,我们从使用开始了解
引入依赖
1<dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>transmittable-thread-local</artifactId> 4 <version>latest</version> 5</dependency>
在使用TTL时,线程需要经过TTL封装,线程池同理
1public 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}
打印结果如下,子线程每次都会获取父线程的ThreadLocal
1子线程ThreadLocal:B 2父线程修改ThreadLocal为C 3子线程ThreadLocal:C 4子线程修改ThreadLocal为D 5子线程ThreadLocal:C
从使用上看,TTL要求将任务封装,那我们就从ThreadLocal和ExecutorService两部分入手
4.2.2、TTL对ThreadLocal的封装
下面是TTL的取值和赋值逻辑,都涉及一个关键方法addThisToHolder,对应的属性holder会在线程池执行任务时用到
1//TransmittableThreadLocal.addThisToHolder() 2private 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 12@Override 13public final void set(T value) { 14 if (!disableIgnoreNullValueSemantics && null == value) { 15 remove(); 16 } else { 17 super.set(value); 18 //当主线程赋值时,会将自己的TTL放到自己的map中 19 addThisToHolder(); 20 } 21} 22 23@Override 24public final T get() { 25 T value = super.get(); 26 if (disableIgnoreNullValueSemantics || null != value) 27 addThisToHolder(); 28 return value; 29}
4.2.3、TTL对任务的封装
1//我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装 2public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) { 3 if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) { 4 return executorService; 5 } 6 //入参是线程池,通过包装类代理线程池的操作 7 return new ExecutorServiceTtlWrapper(executorService); 8} 9 10//ExecutorServiceTtlWrapper.submit() 11public Future<?> submit(@NonNull Runnable task) { 12 //将提交的任务进行封装 13 return executorService.submit(TtlRunnable.get(task)); 14}
4.2.3.1、任务构建
TtlRunnable构造方法
这里都是主线程在操作,因为任务是主线程提交的
1private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { 2 this.capturedRef = new AtomicReference<Object>(capture()); 3 this.runnable = runnable; 4 this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; 5}
这里有一个关键属性capturedRef,他是一个原子引用,存了TTL
1//TrasmitableThreadLocal.Transmitter 2public static Object capture() { 3 //获取ttl的值构建快照 4 return new Snapshot(captureTtlValues(), captureThreadLocalValues()); 5} 6 7private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() { 8 HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>(); 9 for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { 10 //将主线程TTL的值存到当前任务中 11 ttl2Value.put(threadLocal, threadLocal.copyValue()); 12 } 13 return ttl2Value; 14}
4.2.3.2、任务执行
任务执行的代码如下,在任务执行前回放ThreadLocal,在任务执行后恢复ThreadLocal:
这里都是子线程在操作,因为任务都是子线程执行的
1@Override 2public void run() { 3 Object captured = capturedRef.get(); 4 if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { 5 throw new IllegalStateException("TTL value reference is released after run!"); 6 } 7 //1、备份子线程ThreadLocal 8 //2、使用主线程提交任务时构建的ThreadLocal副本,将子线程ThreadLocal覆盖 9 Object backup = replay(captured); 10 try { 11 //3、任务执行 12 runnable.run(); 13 } finally { 14 //3、使用之前备份的子线程ThreadLocal进行恢复 15 restore(backup); 16 } 17}
总结一下,TTL让子线程感知父线程变化的核心思路是,主线程在任务提交时构建ThreadLocal副本,在子线程执行任务时供其使用
⚠️提交和执行任务会对TTL进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略
作者:京东物流 刘朝永
来源:京东云开发者 自猿其说
