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