赞
踩
目录
使用示例
实现原理
作为JAVA开发者,对ThreadLocal应该不陌生,这是JDK提供一个基于线程间变量传递的工具类。在日常项目中,经常会有场景涉及到变量在线程间的传递过程,特别是在多线程开发过程中,保证线程间数据传递的准确性至关重要,针对不同的场景,ThreadLocal在原有的基础版本上也提供了多种变种实现,今天我们就一起看看这些工具类的使用方式。
ThreadLocal是JDK默认提供的一个基于线程内部传递的工具类,用于实现线程局部变量的机制。它提供了一种在多线程环境下,每个线程都拥有自己独立变量副本的方式。使用 ThreadLocal
可以在不同线程之间隔离数据,确保每个线程有自己的数据副本,从而避免多个线程之间的竞争和冲突。
ThreadLocal 使用一个内部类 ThreadLocalMap 来管理每个线程的数据。每个 ThreadLocal 对象在线程的 ThreadLocalMap 中都有一个唯一的键值对,其键为 ThreadLocal 对象自身,值为线程特定的数据。
每个线程都有自己的 Thread 对象,Thread 对象中有一个 ThreadLocalMap 类型的成员变量 threadLocals,用于保存该线程中所有的 ThreadLocal 对象和其对应的值。
在使用 ThreadLocal 的线程中,当调用 ThreadLocal 的 set 方法时,会先获取当前线程的 threadLocals,然后将 ThreadLocal 对象作为键,要保存的值作为值,存储到 threadLocals 中。
当线程需要获取 ThreadLocal 对象的值时,调用 ThreadLocal 的 get 方法,通过当前线程的 threadLocals 获取对应的值。
每个 ThreadLocal 对象的实例都独立维护着一个 HashMap,这个 HashMap 的键是线程对象,值是该线程对象对应的值。这样就实现了每个线程都独立存储和获取自己的数据。
当线程结束或者被回收时,threadLocals 中的数据也会随之被回收,从而避免内存泄漏。
ThreadLocal.set()
- public void set(T value) {
- Thread t = Thread.currentThread();
- //获取当前线程的ThreadLocalMap变量
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
ThreadLocal.get()
- 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();
- }
- @Test
- public void test_threadLocal() throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
-
- ThreadLocal<String> tl1 = new ThreadLocal<>();
- tl1.set("test");
-
- new Thread(() -> {
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.countDown();
- }).start();
-
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.await();
- }
-
- 输出:
- Thread-4: null
- Test worker: test
ThreadLocal可以在线程内部传递变量,但是如果我想在线程间传递变量就没办法满足,这个也是ThreadLoacal本身的局限性。好在JDK针对线程间传递变量的场景也提供了一个工具类InheritableThreadLocal。InheritableThreadLocal是ThreadLocal的一个子类,它扩展了ThreadLocal的功能,可以让子线程继承父线程中设置的局部变量值。
InheritableThreadLocal的核心实现是在创建Thread线程的时候,会从父线程中拷贝变量信息到子线程
InheritableThreadLocal 的实现原理与 ThreadLocal 类似,都是通过 Thread 类中的ThreadLocalMap来管理每个线程中的数据,不同的是InheritableThreadLocal 对于子线程的数据继承进行了特殊处理。
以下是 InheritableThreadLocal 的简要实现原理:
InheritableThreadLocal 是 ThreadLocal 的子类,它重写了 ThreadLocal 的三个方法:childValue()、getMap() 和 createMap()。
在父线程中,当调用 InheritableThreadLocal 的 set() 方法时,会先获取当前线程的 threadLocals,然后将 InheritableThreadLocal 对象作为键,要保存的值作为值,存储到 threadLocals 中。
在创建子线程时,子线程会调用父线程的 inheritableThreadLocals 的 childValue() 方法,该方法会创建一个新的 InheritableThreadLocal 实例,并使用父线程中存储的 InheritableThreadLocal 的值作为初始值。
子线程在创建时,会将父线程的 inheritableThreadLocals 复制到自己的 threadLocals 中,实现了父线程数据向子线程的继承。
当子线程修改自己的 InheritableThreadLocal 的值时,并不会影响到其他线程,因为每个线程都有自己独立的 InheritableThreadLocal 对象。
InheritableThreadLocal 与 ThreadLocal 的区别在于,对于子线程的 InheritableThreadLocal 的值,它的创建和赋值是通过 childValue() 方法进行的,而不是直接复制父线程的值。
- public class InheritableThreadLocal<T> extends ThreadLocal<T> {
-
- /**
- * 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
- */
- protected T childValue(T parentValue) {
- return parentValue;
- }
-
- /**
- * 由于重写了getMap,操作InheritableThreadLocal时,将只影响Thread类中的inheritableThreadLocals变量,
- * 与threadLocals变量不再有关系
- */
- ThreadLocalMap getMap(Thread t) {
- return t.inheritableThreadLocals;
- }
-
- /**
- * 类似于getMap功能,保证操作的是inheritableThreadLocals变量
- * 与threadLocals变量不再有关系
- */
- void createMap(Thread t, T firstValue) {
- t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
- }
- }
查看Thread类可以发现是有两个ThreadLocalMap类型的属性,刚好对应了我们的threadLocal和inheritableThreadLocal:
线程变量继承的操作是在线程创建的时候实现的,我们看下线程创建时候的源码执行的init方法。
- /**
- * 通过Ruannale和继承Thread方式创建的线程inheritThreadLocals都为true
- */
- private void init(ThreadGroup g, Runnable target, String name,
- long stackSize, AccessControlContext acc,
- boolean inheritThreadLocals) {
- ......(其他代码)
-
- // 拷贝父线程的inheritableThreadLocals数据
- if (inheritThreadLocals && parent.inheritableThreadLocals != null)
- this.inheritableThreadLocals =
- ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
-
- ......(其他代码)
- }
上述条件中parent.inheritableThreadLocals为什么不为空,这里就涉及到上面说的重写了,ThreadLocal的set源码:
- public void set(T value) {
- Thread t = Thread.currentThread();
- //默认这里的mao是threadlLocal,
- //但是inheritableThreadLocal重写了getMap方法,使用的是inheritableThreadLocals
- //所以这个值就设置到inheritableThreadLocals里了
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- @Test
- public void test_inheritable_threadLocal() throws Exception {
- CountDownLatch latch = new CountDownLatch(3);
- ExecutorService executorService = Executors.newSingleThreadExecutor(NamedThreadFactory.create("pool"));
-
- InheritableThreadLocal<String> tl1 = new InheritableThreadLocal<>();
- tl1.set("test");
-
- new Thread(() -> {
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.countDown();
- }).start();
-
- tl1.set("test2");
- executorService.execute((() -> {
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.countDown();
- }));
-
- tl1.set("test3");
- executorService.execute((() -> {
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.countDown();
- }));
-
- System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
- latch.await();
- }
-
- 输出:
- Thread-4: test
- Test worker: test3
- pool-thread-1: test2
- pool-thread-1: test2
InheritableThreadLocal可以实现线程间的变量传递,但是只局限于父子线程,从上面原理解析中可以看到具体的传递是在创建线程的步骤。那么针对线程池这种线程可复用的场景就不适用了,这里就需要使用另外一种ThreadLocal扩展TransmittableThreadLocal。TransmittableThreadLocal 是一个与线程绑定的变量,它扩展了 InheritableThreadLocal 的功能,并提供了更灵活的线程数据传递和控制。TransmittableThreadLocal 是使用 Alibaba 的开源库 - transmittable-thread-local 提供的。它在跨线程传递数据时,不仅可以像 InheritableThreadLocal 一样将数据从父线程传递给子线程,还可以在线程池、异步任务等特殊场景中正确地传递线程数据。
当我们使用线程池时,需要使用TtlRunnable.get(runnable)对runnable进行包装,或者使用TtlExecutors.getTtlExecutor(executor)对执行器进行包装,才能使线程池的变量传递起效果。在创建TtlRunnable的过程中,会从父线程中获取线程变量进行拷贝。
构建TtlRunnable
- private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
- // 原子引用
- this.capturedRef = new AtomicReference<Object>(capture());
- this.runnable = runnable;
- this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
- }
capture捕获父线程的ttl
-
- // 存放父线程的值
- 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>();
- // 遍历了所有holder
- for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
- // copyValue实际上调用了TransmittableThreadLocal的get方法获取线程存储的变量值
- 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;
- }
TtlRunnable的run方法
- public void run() {
- // 获取Snapshot对象,里面存储了父线程的值
- final Object captured = capturedRef.get();
- if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
- throw new IllegalStateException("TTL value reference is released after run!");
- }
-
- // 传入capture方法捕获的ttl,然后在子线程重放,也就是调用ttl的set方法,
- // 这样就会把值设置到当前的线程中去,最后会把子线程之前存在的ttl返回
- final Object backup = replay(captured);
- try {
- // 调用原runnable的run
- runnable.run();
- } finally {
- //
- restore(backup);
- }
-
- }
对线程池的包装:
- /**
- * 请求调用上下文辅助类
- * 用户包装线程池,实现上下文信息传递
- */
- public class ExecutorUtils {
-
- public static ExecutorService getTtlExecutorService(ExecutorService executorService) {
- return TtlExecutors.getTtlExecutorService(executorService);
- }
-
- public static ScheduledExecutorService getTtlScheduledExecutorService(ScheduledExecutorService executorService) {
- return TtlExecutors.getTtlScheduledExecutorService(executorService);
- }
- }
使用TTL封装的RequestContext:
-
- /**
- * 请求调用上下文辅助类
- */
- public class RequestContextHolder {
-
- private static TransmittableThreadLocal<RequestContext> requestContextHolder
- = new TransmittableThreadLocal<>();
-
- public static RequestContext getRequestContext() {
- return requestContextHolder.get();
- }
-
- public static void resetRequestContext() {
- requestContextHolder.remove();
- }
-
- public static void setRequestContext(RequestContext requestContext) {
- if (null == requestContext) {
- resetRequestContext();
- } else {
- requestContextHolder.set(requestContext);
- }
- }
-
- public static String getTenantId() {
- if (null != getRequestContext()) {
- return getRequestContext().getTenantId();
- }
- return null;
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。