背景
在Java并发编程中,线程上下文(Thread Context)是指与执行线程相关的环境和状态信息。这包括线程的局部变量、事务上下文、安全凭证、用户会话信息等。在多线程应用程序中,每个线程可能需要处理不同的任务,这些任务可能需要访问和修改与当前线程相关的特定数据。通常有下列常见的使用场景。
-
数据隔离:在并发编程中,数据隔离是保证线程安全的关键。使用线程上下文可以确保每个线程都有自己的数据副本,从而避免了数据共享时的并发问题。
-
事务管理:在处理数据库事务时,可能需要确保在同一个线程中执行的所有数据库操作都在同一个事务上下文中。通过线程上下文可以轻松实现这一点。
-
用户会话管理:在Web应用中,通常需要跟踪用户的会话信息。通过将会话信息存储在线程上下文中,可以确保在处理用户请求的整个过程中,都能访问到相应的会话信息。
-
安全信息传递:在执行需要权限验证的操作时,可以将用户的安全凭证存储在线程上下文中,这样在访问受限资源时可以方便地进行权限检查。
在Java体系下常见的实现方式有TheadLocal、InheritableThreadLocal、TransmittableThreadLocal(开源工具)
TheadLocal
在当前线程中使用TheadLocal,在调用栈的方法中直接通TheadLocal获取值简化操作
public class ThreadLocalDemo {
// 创建ThreadLocal变量
private static final ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
@Test
public void curThread() {
threadLocalValue.set(1);
printThreadLocalValue();
}
private void printThreadLocalValue() {
System.out.println("Thread: " + Thread.currentThread().getName() + ", value: " + threadLocalValue.get());
}
// 输出:Thread: main, value: 1
}
当我们要在子线程中访问则会出现问题,子线程无法访问到数据,这时候就要用到 InheritableThreadLocal
@Test
public void childThread() throws Exception{
threadLocalValue.set(1);
// 线程1:设置并获取ThreadLocal变量的值
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1: " + threadLocalValue.get()); // 获取值
});
thread1.start();
thread1.join(); // 等待线程1执行完毕
}
// 输出: Thread 1: null
InheritableThreadLocal
InheritableThreadLocal是ThreadLocal的一个扩展,它提供了一种机制,使得一个线程可以访问其父线程中设置的局部变量。这在需要在父线程和子线程之间共享数据时非常有用,比如在处理用户请求的Web应用中,可能需要在多个线程之间共享用户的会话信息或安全凭证。
-
使用方式:InheritableThreadLocal的使用方式与ThreadLocal类似,主要区别在于InheritableThreadLocal允许子线程访问在父线程中设置的值。
-
创建InheritableThreadLocal变量:通过new InheritableThreadLocal()的方式创建,其中T是你希望存储的数据类型。
-
设置值:在父线程中使用set(T value)方法来为变量设置一个值。
-
获取值:在子线程中使用get()方法来获取从父线程继承的值。
-
移除值:使用remove()方法来移除当前线程的值,以避免内存泄漏。
private static final InheritableThreadLocal<Integer> iHThreadLocalValue = new InheritableThreadLocal<>();
@Test
public void childThread_ihTl() throws Exception{
iHThreadLocalValue.set(1);
// 线程1:设置并获取ThreadLocal变量的值
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1: " + iHThreadLocalValue.get()); // 获取值
});
thread1.start();
thread1.join(); // 等待线程1执行完毕
}
在创建线程时通过方法完成上下文的传递 java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)
现成维护了inheritableThreadLocals,通过父线程引用,最终实现父子线程传递。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
但我们经常通过线程池使用线程,在池化场景下有会有新问题,
- 线程提前创建好,如果要继续使用不同新的上下文则会出现无法获取的情况。
- 上下文被线程共享相互修改会引发非预期的问题
@Test
public void testInheritableInPool() throws Exception{
InheritableThreadLocal<Integer> in = new InheritableThreadLocal<>();
in.set(1);
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<?> submit1 = executorService.submit(() -> {
System.out.println(in.get());
});
submit1.get();
InheritableThreadLocal<Integer> in2 = new InheritableThreadLocal<>();
in2.set(2);
Future<?> submit2 = executorService.submit(() -> {
System.out.println(in2.get());
});
submit2.get();
}
输出:
1
null
这时候可以考虑使用 TransmittableThreadLocal
TransmittableThreadLocal
TransmittableThreadLocal是阿里巴巴开源的一个扩展ThreadLocal的工具类,主要用于解决在使用线程池等多线程框架时,ThreadLocal变量无法在父子线程之间传递的问题。它不仅可以在父线程和子线程之间传递数据,还可以在使用线程池时,在线程复用的情况下传递数据。 上述能力基于CRR模式实现。
- 主要特性
- 线程池场景下的上下文传递:TransmittableThreadLocal可以在使用线程池时,将父线程的上下文传递到子线程中,即使线程被复用。
- 支持异步执行:在异步执行任务时,TransmittableThreadLocal也能保证上下文的传递。
- 与ThreadLocal和InheritableThreadLocal兼容:TransmittableThreadLocal的使用方式与ThreadLocal和InheritableThreadLocal类似,易于替换和集成。
@Test
public void ttlPool() throws Exception{
TransmittableThreadLocal<Integer> ttl = new TransmittableThreadLocal<>();
ttl.set(1);
TtlRunnable ttlRunnable = TtlRunnable.get(() -> {
System.out.println(ttl.get());
ttl.set(2);
ttl.remove();
});
ExecutorService executorService = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),new CallerRunsPolicy());
Future<?> submit1 = executorService.submit(ttlRunnable);
submit1.get();
Future<?> submitAfterRm = executorService.submit(
TtlRunnable.get(() -> {
try {
Thread.sleep(1000);
System.out.println(ttl.get());
} catch (Exception e) {
e.getMessage();
}
}
));
submitAfterRm.get();
TransmittableThreadLocal<Integer> tt2 = new TransmittableThreadLocal<>();
tt2.set(12);
Future<?> submit2 = executorService.submit(TtlRunnable.get(() -> {
System.out.println(tt2.get());
}));
submit2.get();
ttl.remove();
输出:
1
1
12
更多的使用细节及原理可参考项目介绍
参考
[1].transmittable,https://github.com/alibaba/transmittable-thread-local?tab=readme-ov-file#-user-guide