众所周知,使用InheritableThreadLocal可以实现父子线程之间的值传递,不过使用InheritableThreadLocal有一个隐藏的问题,子线程在创建的时候会从父线程InheritableThreadLocal中拷贝数据,一旦子线程创建成功以后,父线程更新了数据,对子线程是无效的,这种情况在使用线程池的时候会有数据不安全的问题,下面使用代码演示一下:
public class ITLTest {
// 声明一个线程池
private static final ExecutorService pool = Executors.newFixedThreadPool(2);
// 声明一个InheritableThreadLocal全局变量
private static final ThreadLocal<String> tl = new InheritableThreadLocal<>();
public static void main(String[] args) {
// 设置一个值
tl.set("张三");
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 向线程池提交
pool.submit(r1);
// 声明一个Callable对象
Callable<String> c1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 向线程池提交
pool.submit(c1);
tl.set("李四"); // 更新tl中的值
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 向线程池提交
pool.submit(r2);
// 声明一个Callable对象
Callable<String> c2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 向线程池提交
pool.submit(c2);
// 关闭线程池
pool.shutdown();
}
}
运行结果:
main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->张三
pool-1-thread-2--->张三
可以看到,虽然主线程更新了InheritableThreadLocal中的值,但是子线程依然使用的是旧值,这样就不能保证数据的安全性了。
下面使用TransmittableThreadLocal改造一下,TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。
导入maven依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.1</version>
</dependency>
TransmittableThreadLocal需要配合一些方法的使用,下面详细介绍一下使用方法:
- 针对单个线程,需要配合TtlRunnable 或TtlCallable使用:
public class TTLTest2 {
// 声明一个线程池
private static final ExecutorService pool = Executors.newFixedThreadPool(2);
// 声明一个TransmittableThreadLocal全局变量
private static final ThreadLocal<String> tl = new TransmittableThreadLocal<>();
public static void main(String[] args) {
// 设置一个值
tl.set("张三");
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 使用TtlRunnable包装一下Runnable对象
TtlRunnable ttlRunnable1 = TtlRunnable.get(r1);
// 向线程池提交
pool.submit(ttlRunnable1);
// 声明一个Callable对象
Callable<String> c1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 使用TtlCallable包装一下Callable对象
TtlCallable<String> ttlCallable1 = TtlCallable.get(c1);
// 向线程池提交
pool.submit(ttlCallable1);
tl.set("李四"); // 更新tl中的值
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 使用TtlRunnable包装一下Runnable对象
TtlRunnable ttlRunnable2 = TtlRunnable.get(r2);
// 向线程池提交
pool.submit(ttlRunnable2);
// 声明一个Callable对象
Callable<String> c2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 使用TtlCallable包装一下Callable对象
TtlCallable<String> ttlCallable2 = TtlCallable.get(c2);
// 向线程池提交
pool.submit(ttlCallable2);
// 关闭线程池
pool.shutdown();
}
}
运行结果:
main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->李四
pool-1-thread-2--->李四
可以看到,主线程更新值以后,子线程中的值也更新了,可是这种方法不免有些太过麻烦,每声明一个线程都要包装一下,对此,可以针对整个线程池进行包装。
- 针对整个线程池,需要配合TtlExecutors使用:
public class TTLTest {
// 声明一个线程池
private static final ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
// 声明一个TransmittableThreadLocal全局变量
private static final ThreadLocal<String> tl = new TransmittableThreadLocal<>();
public static void main(String[] args) {
// 设置一个值
tl.set("张三");
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 向线程池提交
pool.submit(r1);
// 声明一个Callable对象
Callable<String> c1 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 向线程池提交
pool.submit(c1);
tl.set("李四"); // 更新tl中的值
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
// 声明一个Runnable对象
Runnable r2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
};
// 向线程池提交
pool.submit(r2);
// 声明一个Callable对象
Callable<String> c2 = () -> {
System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
return "success";
};
// 向线程池提交
pool.submit(c2);
// 关闭线程池
pool.shutdown();
}
}
运行结果:
main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->李四
pool-1-thread-2--->李四
可以看到,使用TtlExecutors.getTtlExecutorService方法包装线程池对象以后,主线程更新值以后,子线程也动态更新了。