当前位置:   article > 正文

ThreadLocal的多种实现详解_treadlocal变种

treadlocal变种

目录

1、ThreadLocal

实现原理

源码解析

使用示例​​​​​​​

2、InheritableThreadLocal

实现原理

源码解析

使用示例

3、TransmittableThreadLocal

实现原理​​​​​​​

源码解析 

使用示例


作为JAVA开发者,对ThreadLocal应该不陌生,这是JDK提供一个基于线程间变量传递的工具类。在日常项目中,经常会有场景涉及到变量在线程间的传递过程,特别是在多线程开发过程中,保证线程间数据传递的准确性至关重要,针对不同的场景,ThreadLocal在原有的基础版本上也提供了多种变种实现,今天我们就一起看看这些工具类的使用方式。

1、ThreadLocal


ThreadLocal是JDK默认提供的一个基于线程内部传递的工具类,用于实现线程局部变量的机制。它提供了一种在多线程环境下,每个线程都拥有自己独立变量副本的方式。使用 ThreadLocal 可以在不同线程之间隔离数据,确保每个线程有自己的数据副本,从而避免多个线程之间的竞争和冲突。

实现原理

ThreadLocal 使用一个内部类 ThreadLocalMap 来管理每个线程的数据。每个 ThreadLocal 对象在线程的 ThreadLocalMap 中都有一个唯一的键值对,其键为 ThreadLocal 对象自身,值为线程特定的数据。

  1. 每个线程都有自己的 Thread 对象,Thread 对象中有一个 ThreadLocalMap 类型的成员变量 threadLocals,用于保存该线程中所有的 ThreadLocal 对象和其对应的值。

  2. 在使用 ThreadLocal 的线程中,当调用 ThreadLocal 的 set 方法时,会先获取当前线程的 threadLocals,然后将 ThreadLocal 对象作为键,要保存的值作为值,存储到 threadLocals 中。

  3. 当线程需要获取 ThreadLocal 对象的值时,调用 ThreadLocal 的 get 方法,通过当前线程的 threadLocals 获取对应的值。

  4. 每个 ThreadLocal 对象的实例都独立维护着一个 HashMap,这个 HashMap 的键是线程对象,值是该线程对象对应的值。这样就实现了每个线程都独立存储和获取自己的数据。

  5. 当线程结束或者被回收时,threadLocals 中的数据也会随之被回收,从而避免内存泄漏。

源码解析

ThreadLocal.set()

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. //获取当前线程的ThreadLocalMap变量
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. }

ThreadLocal.get()

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

使用示例

  1. @Test
  2. public void test_threadLocal() throws Exception {
  3. CountDownLatch latch = new CountDownLatch(1);
  4. ThreadLocal<String> tl1 = new ThreadLocal<>();
  5. tl1.set("test");
  6. new Thread(() -> {
  7. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  8. latch.countDown();
  9. }).start();
  10. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  11. latch.await();
  12. }
  13. 输出:
  14. Thread-4: null
  15. Test worker: test

2、InheritableThreadLocal


ThreadLocal可以在线程内部传递变量,但是如果我想在线程间传递变量就没办法满足,这个也是ThreadLoacal本身的局限性。好在JDK针对线程间传递变量的场景也提供了一个工具类InheritableThreadLocal。InheritableThreadLocal是ThreadLocal的一个子类,它扩展了ThreadLocal的功能,可以让子线程继承父线程中设置的局部变量值。

实现原理

InheritableThreadLocal的核心实现是在创建Thread线程的时候,会从父线程中拷贝变量信息到子线程

InheritableThreadLocal 的实现原理与 ThreadLocal 类似,都是通过 Thread 类中的ThreadLocalMap来管理每个线程中的数据,不同的是InheritableThreadLocal 对于子线程的数据继承进行了特殊处理。

以下是 InheritableThreadLocal 的简要实现原理:

  1. InheritableThreadLocal 是 ThreadLocal 的子类,它重写了 ThreadLocal 的三个方法:childValue()、getMap() 和 createMap()。

  2. 在父线程中,当调用 InheritableThreadLocal 的 set() 方法时,会先获取当前线程的 threadLocals,然后将 InheritableThreadLocal 对象作为键,要保存的值作为值,存储到 threadLocals 中。

  3. 在创建子线程时,子线程会调用父线程的 inheritableThreadLocals 的 childValue() 方法,该方法会创建一个新的 InheritableThreadLocal 实例,并使用父线程中存储的 InheritableThreadLocal 的值作为初始值。

  4. 子线程在创建时,会将父线程的 inheritableThreadLocals 复制到自己的 threadLocals 中,实现了父线程数据向子线程的继承。

  5. 当子线程修改自己的 InheritableThreadLocal 的值时,并不会影响到其他线程,因为每个线程都有自己独立的 InheritableThreadLocal 对象。

  6. InheritableThreadLocal 与 ThreadLocal 的区别在于,对于子线程的 InheritableThreadLocal 的值,它的创建和赋值是通过 childValue() 方法进行的,而不是直接复制父线程的值。

源码解析

  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2. /**
  3. * 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
  4. */
  5. protected T childValue(T parentValue) {
  6. return parentValue;
  7. }
  8. /**
  9. * 由于重写了getMap,操作InheritableThreadLocal时,将只影响Thread类中的inheritableThreadLocals变量,
  10. * 与threadLocals变量不再有关系
  11. */
  12. ThreadLocalMap getMap(Thread t) {
  13. return t.inheritableThreadLocals;
  14. }
  15. /**
  16. * 类似于getMap功能,保证操作的是inheritableThreadLocals变量
  17. * 与threadLocals变量不再有关系
  18. */
  19. void createMap(Thread t, T firstValue) {
  20. t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  21. }
  22. }


查看Thread类可以发现是有两个ThreadLocalMap类型的属性,刚好对应了我们的threadLocal和inheritableThreadLocal:

线程变量继承的操作是在线程创建的时候实现的,我们看下线程创建时候的源码执行的init方法。 

  1. /**
  2. * 通过Ruannale和继承Thread方式创建的线程inheritThreadLocals都为true
  3. */
  4. private void init(ThreadGroup g, Runnable target, String name,
  5. long stackSize, AccessControlContext acc,
  6. boolean inheritThreadLocals) {
  7. ......(其他代码)
  8. // 拷贝父线程的inheritableThreadLocals数据
  9. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  10. this.inheritableThreadLocals =
  11. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  12. ......(其他代码)
  13. }

上述条件中parent.inheritableThreadLocals为什么不为空,这里就涉及到上面说的重写了,ThreadLocal的set源码:

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. //默认这里的mao是threadlLocal,
  4. //但是inheritableThreadLocal重写了getMap方法,使用的是inheritableThreadLocals
  5. //所以这个值就设置到inheritableThreadLocals里了
  6. ThreadLocalMap map = getMap(t);
  7. if (map != null)
  8. map.set(this, value);
  9. else
  10. createMap(t, value);
  11. }

使用示例

  1. @Test
  2. public void test_inheritable_threadLocal() throws Exception {
  3. CountDownLatch latch = new CountDownLatch(3);
  4. ExecutorService executorService = Executors.newSingleThreadExecutor(NamedThreadFactory.create("pool"));
  5. InheritableThreadLocal<String> tl1 = new InheritableThreadLocal<>();
  6. tl1.set("test");
  7. new Thread(() -> {
  8. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  9. latch.countDown();
  10. }).start();
  11. tl1.set("test2");
  12. executorService.execute((() -> {
  13. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  14. latch.countDown();
  15. }));
  16. tl1.set("test3");
  17. executorService.execute((() -> {
  18. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  19. latch.countDown();
  20. }));
  21. System.out.println(Thread.currentThread().getName() + ": " + tl1.get());
  22. latch.await();
  23. }
  24. 输出:
  25. Thread-4: test
  26. Test worker: test3
  27. pool-thread-1: test2
  28. pool-thread-1: test2

3、TransmittableThreadLocal


InheritableThreadLocal可以实现线程间的变量传递,但是只局限于父子线程,从上面原理解析中可以看到具体的传递是在创建线程的步骤。那么针对线程池这种线程可复用的场景就不适用了,这里就需要使用另外一种ThreadLocal扩展TransmittableThreadLocal。TransmittableThreadLocal 是一个与线程绑定的变量,它扩展了 InheritableThreadLocal 的功能,并提供了更灵活的线程数据传递和控制。TransmittableThreadLocal 是使用 Alibaba 的开源库 - transmittable-thread-local 提供的。它在跨线程传递数据时,不仅可以像 InheritableThreadLocal 一样将数据从父线程传递给子线程,还可以在线程池、异步任务等特殊场景中正确地传递线程数据。

实现原理

当我们使用线程池时,需要使用TtlRunnable.get(runnable)对runnable进行包装,或者使用TtlExecutors.getTtlExecutor(executor)对执行器进行包装,才能使线程池的变量传递起效果。在创建TtlRunnable的过程中,会从父线程中获取线程变量进行拷贝。​​​​​​​

源码解析 

构建TtlRunnable

  1. private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
  2. // 原子引用
  3. this.capturedRef = new AtomicReference<Object>(capture());
  4. this.runnable = runnable;
  5. this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
  6. }
  • ​​​​​​​

capture捕获父线程的ttl

  1. // 存放父线程的值
  2. public static Object capture() {
  3. return new Snapshot(captureTtlValues(), captureThreadLocalValues());
  4. }
  5. private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
  6. HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
  7. // 遍历了所有holder
  8. for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
  9. // copyValue实际上调用了TransmittableThreadLocal的get方法获取线程存储的变量值
  10. ttl2Value.put(threadLocal, threadLocal.copyValue());
  11. }
  12. return ttl2Value;
  13. }
  14. private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
  15. final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
  16. //
  17. for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
  18. final ThreadLocal<Object> threadLocal = entry.getKey();
  19. final TtlCopier<Object> copier = entry.getValue();
  20. //
  21. threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
  22. }
  23. return threadLocal2Value;
  24. }

TtlRunnable的run方法

  1. public void run() {
  2. // 获取Snapshot对象,里面存储了父线程的值
  3. final 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. // 传入capture方法捕获的ttl,然后在子线程重放,也就是调用ttl的set方法,
  8. // 这样就会把值设置到当前的线程中去,最后会把子线程之前存在的ttl返回
  9. final Object backup = replay(captured);
  10. try {
  11. // 调用原runnable的run
  12. runnable.run();
  13. } finally {
  14. //
  15. restore(backup);
  16. }
  17. }

使用示例

对线程池的包装:

  1. /**
  2. * 请求调用上下文辅助类
  3. * 用户包装线程池,实现上下文信息传递
  4. */
  5. public class ExecutorUtils {
  6. public static ExecutorService getTtlExecutorService(ExecutorService executorService) {
  7. return TtlExecutors.getTtlExecutorService(executorService);
  8. }
  9. public static ScheduledExecutorService getTtlScheduledExecutorService(ScheduledExecutorService executorService) {
  10. return TtlExecutors.getTtlScheduledExecutorService(executorService);
  11. }
  12. }

使用TTL封装的RequestContext:

  1. /**
  2. * 请求调用上下文辅助类
  3. */
  4. public class RequestContextHolder {
  5. private static TransmittableThreadLocal<RequestContext> requestContextHolder
  6. = new TransmittableThreadLocal<>();
  7. public static RequestContext getRequestContext() {
  8. return requestContextHolder.get();
  9. }
  10. public static void resetRequestContext() {
  11. requestContextHolder.remove();
  12. }
  13. public static void setRequestContext(RequestContext requestContext) {
  14. if (null == requestContext) {
  15. resetRequestContext();
  16. } else {
  17. requestContextHolder.set(requestContext);
  18. }
  19. }
  20. public static String getTenantId() {
  21. if (null != getRequestContext()) {
  22. return getRequestContext().getTenantId();
  23. }
  24. return null;
  25. }
  26. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/948961
推荐阅读
相关标签
  

闽ICP备14008679号