ThreadLocal的多种实现详解

目录

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()

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

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() 方法进行的,而不是直接复制父线程的值。

源码解析

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

3、TransmittableThreadLocal


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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线程的状态以及各状态之间的转换详解.mp4 线程的初始化,中断以及其源码讲解.mp4 多种创建线程的方式案例演示(一)带返回值的方式.mp4 多种创建线程的方式案例演示(二)使用线程池.mp4 Spring对并发的支持:Spring的异步任务.mp4 使用jdk8提供的lambda进行并行计算.mp4 了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 AbstractQueuedSynchronizer(AQS)详解.mp4 使用AQS重写自己的锁.mp4 重入锁原理与演示.mp4 读写锁认识与原理.mp4 细读ReentrantReadWriteLock源码.mp4 ReentrantReadWriteLock锁降级详解.mp4 线程安全性问题简单总结.mp4 线程之间的通信之wait notify.mp4 通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与实现原理剖析.mp4 ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析.mp4 提前完成任务之FutureTask使用.mp4 Future设计模式实现实现类似于JDK提供的Future).mp4 Future源码解读.mp4 ForkJoin框架详解.mp4 同步容器与并发容器.mp4 并发容器CopyOnWriteArrayList原理与使用.mp4 并发容器ConcurrentLinkedQueue原理与使用.mp4 Java中的阻塞队列原理与使用.mp4 实战:简单实现消息队列.mp4 并发容器ConcurrentHashMap原理与使用.mp4 线程池的原理与使用.mp4 Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二).mp4 JDK8的新增原子操作类LongAddr原理与使用.mp4 JDK8新增锁StampedLock详解.mp4 重排序问题.mp4 happens-before简单概述.mp4 锁的内存语义.mp4 volatile内存语义.mp4 final域的内存语义.mp4 实战:问题定位.mp4

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值