我们都知道ThreadLocal是可以在一个线程中当容器使用的局部变量,是线程隔离、线程安全的。
但是如果子线程要获取父线程的变量,便不太方便。比方在业务代码中,为了提高响应速度,将多个复杂、长时间的计算或调用过程异步进行,让主线程可以先进行其他操作。异步子线程可能会用到主线程ThreadLocal中的内容,比方链路追踪。
下面我们一步步来解析:
一、ThreadLocal
/**
* ThreadLocal
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> parent = new ThreadLocal<>();
parent.set(Thread.currentThread().getName() + "=======hello,myThread00");
new Thread(() -> {
try {
// 设置本线程变量
parent.set(Thread.currentThread().getName() + "=======hello,myThread01");
// dosomething
Thread.sleep(3000);
// 使用线程变量
System.out.println(Thread.currentThread().getName() + ":" + parent.get());
// 清除
parent.remove();
// do other thing
//.....
} catch (Exception e) {
e.printStackTrace();
}
}, "thread-1").start();
new Thread(() -> {
try {
// 设置本线程变量
parent.set(Thread.currentThread().getName() + "=======hello,myThread02");
// dosomething
Thread.sleep(4000);
// 使用线程变量
System.out.println(Thread.currentThread().getName() + ":" + parent.get());
// 清除
parent.remove();
// do other thing
//.....
} catch (Exception e) {
e.printStackTrace();
}
}, "thread-2").start();
System.out.println(Thread.currentThread().getName() + ":" + parent.get());
}
运行结果:
"C:\Program Files\Java\jdk-11.0.11\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=53226:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\workspace2021\study\target\classes;D:\Repository\com\freewayso\image-combiner\2.3.2\image-combiner-2.3.2.jar;D:\Repository\org\tinylog\tinylog\1.3.6\tinylog-1.3.6.jar;D:\Repository\org\springframework\boot\spring-boot-starter-web\2.6.2\spring-boot-starter-web-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter\2.6.2\spring-boot-starter-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter-logging\2.6.2\spring-boot-starter-logging-2.6.2.jar;D:\Repository\ch\qos\logback\logback-classic\1.2.9\logback-classic-1.2.9.jar;D:\Repository\ch\qos\logback\logback-core\1.2.9\logback-core-1.2.9.jar;D:\Repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.0\log4j-to-slf4j-2.17.0.jar;D:\Repository\org\apache\logging\log4j\log4j-api\2.17.0\log4j-api-2.17.0.jar;D:\Repository\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;D:\Repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\Repository\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;D:\Repository\org\springframework\boot\spring-boot-starter-json\2.6.2\spring-boot-starter-json-2.6.2.jar;D:\Repository\com\fasterxml\jackson\core\jackson-databind\2.13.1\jackson-databind-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-annotations\2.13.1\jackson-annotations-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-core\2.13.1\jackson-core-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.1\jackson-datatype-jdk8-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.1\jackson-datatype-jsr310-2.13.1.jar;D:\Repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.1\jackson-module-parameter-names-2.13.1.jar;D:\Repository\org\springframework\boot\spring-boot-starter-tomcat\2.6.2\spring-boot-starter-tomcat-2.6.2.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.56\tomcat-embed-core-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.56\tomcat-embed-el-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.56\tomcat-embed-websocket-9.0.56.jar;D:\Repository\org\springframework\spring-web\5.3.14\spring-web-5.3.14.jar;D:\Repository\org\springframework\spring-beans\5.3.14\spring-beans-5.3.14.jar;D:\Repository\org\springframework\spring-webmvc\5.3.14\spring-webmvc-5.3.14.jar;D:\Repository\org\springframework\spring-aop\5.3.14\spring-aop-5.3.14.jar;D:\Repository\org\springframework\spring-context\5.3.14\spring-context-5.3.14.jar;D:\Repository\org\springframework\spring-expression\5.3.14\spring-expression-5.3.14.jar;D:\Repository\org\springframework\boot\spring-boot-devtools\2.6.2\spring-boot-devtools-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot\2.6.2\spring-boot-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-autoconfigure\2.6.2\spring-boot-autoconfigure-2.6.2.jar;D:\Repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\Repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\Repository\org\springframework\spring-core\5.3.14\spring-core-5.3.14.jar;D:\Repository\org\springframework\spring-jcl\5.3.14\spring-jcl-5.3.14.jar com.example.study.ttl.MyThread01
main:main=======hello,myThread00
thread-1:thread-1=======hello,myThread01
thread-2:thread-2=======hello,myThread02
父子线程各是各的。
子线程能不能直接获取到父线程的?:
/**
* 父子线程各是各的。
* 子线程能不能直接获取到父线程的?: 子线程拿不到父线程的。
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> parent = new ThreadLocal<>();
parent.set(Thread.currentThread().getName() + "=======hello,myThread");
new Thread(() -> {
try {
// 使用线程变量
System.out.println(Thread.currentThread().getName() + ":" + parent.get());
// do other thing
// .....
} catch (Exception e) {
e.printStackTrace();
}
}, "child-thread").start();
}
运行结果:子线程拿不到父线程的。
child-thread:null
二、InheritableThreadLocal
2.1、ITL可用情况:
JDK提供了InheritableThreadLocal可以让子线程拿到父线程的:
示例:
/**
* ITL可用情况:
* JDK提供了InheritableThreadLocal可以让子线程拿到父线程的:
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> parent = new InheritableThreadLocal<>();
parent.set(Thread.currentThread().getName() + "=======hello,myThread");
new Thread(() -> {
try {
// 使用线程变量
System.out.println(Thread.currentThread().getName() + ":" + parent.get());
// do other thing
// .....
} catch (Exception e) {
e.printStackTrace();
}
}, "child-thread").start();
}
运行结果:子线程中拿到了父线程的值。
"C:\Program Files\Java\jdk-11.0.11\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=53493:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\workspace2021\study\target\classes;D:\Repository\com\freewayso\image-combiner\2.3.2\image-combiner-2.3.2.jar;D:\Repository\org\tinylog\tinylog\1.3.6\tinylog-1.3.6.jar;D:\Repository\org\springframework\boot\spring-boot-starter-web\2.6.2\spring-boot-starter-web-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter\2.6.2\spring-boot-starter-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter-logging\2.6.2\spring-boot-starter-logging-2.6.2.jar;D:\Repository\ch\qos\logback\logback-classic\1.2.9\logback-classic-1.2.9.jar;D:\Repository\ch\qos\logback\logback-core\1.2.9\logback-core-1.2.9.jar;D:\Repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.0\log4j-to-slf4j-2.17.0.jar;D:\Repository\org\apache\logging\log4j\log4j-api\2.17.0\log4j-api-2.17.0.jar;D:\Repository\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;D:\Repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\Repository\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;D:\Repository\org\springframework\boot\spring-boot-starter-json\2.6.2\spring-boot-starter-json-2.6.2.jar;D:\Repository\com\fasterxml\jackson\core\jackson-databind\2.13.1\jackson-databind-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-annotations\2.13.1\jackson-annotations-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-core\2.13.1\jackson-core-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.1\jackson-datatype-jdk8-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.1\jackson-datatype-jsr310-2.13.1.jar;D:\Repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.1\jackson-module-parameter-names-2.13.1.jar;D:\Repository\org\springframework\boot\spring-boot-starter-tomcat\2.6.2\spring-boot-starter-tomcat-2.6.2.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.56\tomcat-embed-core-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.56\tomcat-embed-el-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.56\tomcat-embed-websocket-9.0.56.jar;D:\Repository\org\springframework\spring-web\5.3.14\spring-web-5.3.14.jar;D:\Repository\org\springframework\spring-beans\5.3.14\spring-beans-5.3.14.jar;D:\Repository\org\springframework\spring-webmvc\5.3.14\spring-webmvc-5.3.14.jar;D:\Repository\org\springframework\spring-aop\5.3.14\spring-aop-5.3.14.jar;D:\Repository\org\springframework\spring-context\5.3.14\spring-context-5.3.14.jar;D:\Repository\org\springframework\spring-expression\5.3.14\spring-expression-5.3.14.jar;D:\Repository\org\springframework\boot\spring-boot-devtools\2.6.2\spring-boot-devtools-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot\2.6.2\spring-boot-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-autoconfigure\2.6.2\spring-boot-autoconfigure-2.6.2.jar;D:\Repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\Repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\Repository\org\springframework\spring-core\5.3.14\spring-core-5.3.14.jar;D:\Repository\org\springframework\spring-jcl\5.3.14\spring-jcl-5.3.14.jar com.example.study.ttl.MyThread01
child-thread:main=======hello,myThread
2.2、ITL不可用情况:
但是InheritableThreadLocal的继承性是在new Thread创建子线程时候在构造函数内把父线程内线程变量拷贝到子线程内部的。
为了不在创建新线程耗费资源,我们一般会用线程池,线程池的线程会复用,那么线程中的ThreadLocal便不对了,可能是旧的,因为线程是旧的。
如下:代码2向线程池投递3任务,这时候线程池内2个核心线程会被创建,并且队列里面有1个元素。然后代码3休眠4s,旨在让线程池避免饱和执行拒绝策略,然后代码4设置线程变量,代码5提交任务到线程池。运行输出:parent:null,子线程内访问不到父线程设置变量。
// 0.创建线程池
private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor
(2, 2, 1, MINUTES,
new LinkedBlockingQueue<>(1));
public static void main(String[] args) throws InterruptedException {
// 1 创建线程变量
InheritableThreadLocal<String> parent = new InheritableThreadLocal<>();
// 2 投递三个任务,让线程池中的线程全部创建。没有这段则可以拿到。
for (int i = 0; i < 3; ++i) {
bizPoolExecutor.execute(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3休眠4s
Thread.sleep(4000);
// 4.设置线程变量
parent.set("value-set-in-parent");
// 5. 提交任务到线程池
bizPoolExecutor.execute(() -> {
try {
// 5.1访问线程变量
System.out.println("parent:" + parent.get());
} catch (Exception e) {
e.printStackTrace();
}
});
}
运行结果:
parent:null
如果没有2那段代码,则可以拿到结果:
parent:value-set-in-parent
2.3、ITL原理源码:
ThreadLocal 实际上是 Thread 中保存的一个 ThreadLocalMap 类型的属性,与Thread搭配使用。
InheritableThreadLocal 也是如此。
public class Thread implements Runnable {
// 如果单纯使用 ThreadLocal,则 Thread 使用该属性值保存 ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
// 否则使用该属性值
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//inheritThreadLocals默认true
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
Thread parent = currentThread();
…………
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
//Thread的init方法创建新线程时把父线程的拿了过来
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
InheritableThreadLocal是ThreadLocal的子类,复写了几个方法:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
ThreadLocal中对应的方法:
public class ThreadLocal<T> {
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
*/
InheritableThreadLocal通过复写方法,主线程new InheritableThreadLocal(),使Thread中的inheritableThreadLocals不为空,且getMap获取ThreadLocalMap时返回inheritableThreadLocals。childValue 方法用作从父线程中获取值。
则去new Thread创建子线程时,主线程当前线程的parent.inheritableThreadLocals != null,且将主线程的拿了过来。
三、阿里的TransmittableThreadLocal
3.1、TTL使用:
添加pom:
<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.0</version>
</dependency>
示例:1和5处代码变化,添加6。把具体任务使用TtlRunnable.get(task)包装了下,然后再提交到线程池。
package com.example.study.ttl;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MINUTES;
/**
* <h3>study</h3>
* <p></p>
*
* @author : ZhangYuJie
* @date : 2022-02-27 17:22
**/
public class Ttl {
// 0.创建线程池
private static final ThreadPoolExecutor bizPoolExecutor =
new ThreadPoolExecutor(2, 2, 1, MINUTES,
new LinkedBlockingQueue<>(1));
public static void main(String[] args) throws InterruptedException {
// 1 创建线程变量
ThreadLocal<String> parent = new TransmittableThreadLocal<>();
// 2 投递三个任务,让线程池中的线程全部创建。
for (int i = 0; i < 3; ++i) {
bizPoolExecutor.execute(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3休眠4s
Thread.sleep(4000);
// 4.设置线程变量
parent.set("value-set-in-parent");
// 5. 提交任务到线程池
Runnable task = () -> {
try {
// 5.1访问线程变量
System.out.println("parent:" + parent.get());
} catch (Exception e) {
e.printStackTrace();
}
};
// 6、额外的处理,生成修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
bizPoolExecutor.execute(ttlRunnable);
}
}
运行结果:
"C:\Program Files\Java\jdk-11.0.11\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=54305:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath D:\workspace2021\study\target\classes;D:\Repository\com\alibaba\transmittable-thread-local\2.12.0\transmittable-thread-local-2.12.0.jar;D:\Repository\com\freewayso\image-combiner\2.3.2\image-combiner-2.3.2.jar;D:\Repository\org\tinylog\tinylog\1.3.6\tinylog-1.3.6.jar;D:\Repository\org\springframework\boot\spring-boot-starter-web\2.6.2\spring-boot-starter-web-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter\2.6.2\spring-boot-starter-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-starter-logging\2.6.2\spring-boot-starter-logging-2.6.2.jar;D:\Repository\ch\qos\logback\logback-classic\1.2.9\logback-classic-1.2.9.jar;D:\Repository\ch\qos\logback\logback-core\1.2.9\logback-core-1.2.9.jar;D:\Repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.0\log4j-to-slf4j-2.17.0.jar;D:\Repository\org\apache\logging\log4j\log4j-api\2.17.0\log4j-api-2.17.0.jar;D:\Repository\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;D:\Repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\Repository\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;D:\Repository\org\springframework\boot\spring-boot-starter-json\2.6.2\spring-boot-starter-json-2.6.2.jar;D:\Repository\com\fasterxml\jackson\core\jackson-databind\2.13.1\jackson-databind-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-annotations\2.13.1\jackson-annotations-2.13.1.jar;D:\Repository\com\fasterxml\jackson\core\jackson-core\2.13.1\jackson-core-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.1\jackson-datatype-jdk8-2.13.1.jar;D:\Repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.1\jackson-datatype-jsr310-2.13.1.jar;D:\Repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.1\jackson-module-parameter-names-2.13.1.jar;D:\Repository\org\springframework\boot\spring-boot-starter-tomcat\2.6.2\spring-boot-starter-tomcat-2.6.2.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.56\tomcat-embed-core-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.56\tomcat-embed-el-9.0.56.jar;D:\Repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.56\tomcat-embed-websocket-9.0.56.jar;D:\Repository\org\springframework\spring-web\5.3.14\spring-web-5.3.14.jar;D:\Repository\org\springframework\spring-beans\5.3.14\spring-beans-5.3.14.jar;D:\Repository\org\springframework\spring-webmvc\5.3.14\spring-webmvc-5.3.14.jar;D:\Repository\org\springframework\spring-aop\5.3.14\spring-aop-5.3.14.jar;D:\Repository\org\springframework\spring-context\5.3.14\spring-context-5.3.14.jar;D:\Repository\org\springframework\spring-expression\5.3.14\spring-expression-5.3.14.jar;D:\Repository\org\springframework\boot\spring-boot-devtools\2.6.2\spring-boot-devtools-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot\2.6.2\spring-boot-2.6.2.jar;D:\Repository\org\springframework\boot\spring-boot-autoconfigure\2.6.2\spring-boot-autoconfigure-2.6.2.jar;D:\Repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\Repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\Repository\org\springframework\spring-core\5.3.14\spring-core-5.3.14.jar;D:\Repository\org\springframework\spring-jcl\5.3.14\spring-jcl-5.3.14.jar com.example.study.ttl.Ttl
parent:value-set-in-parent
TransmittableThreadLocal完美解决了线程变量继承问题,其是淘宝技术部 哲良开源的一个库,github地址为:https://github.com/alibaba/transmittable-thread-local
3.2、TTL源码解析:
在线程池场景下我们的需求:
InheritableThreadLocal的是在new Thread时候把父线程的inheritableThreadLocals复制到了子线程,从而实现线程变量的继承特性。而现在我们需要在提交任务到线程池前,把父线程中的线程变量保存到任务内,然后等线程池内线程执行任务前把保存的父线程的线程变量复制到线程池中的执行线程上,然后运行我们的任务,等任务运行完毕后,在清除掉执行线程上的线程变量。
可知第一我们需要包装提交到线程池内的任务,里面添加一个变量来保存父线程的线程变量。TransmittableThreadLocal就是这样的思路。
如上代码6就是把我们的任务使用TtlRunnable.get(task);包装了下,其内部就是拷贝父线程中的线程变量到包装的任务内保存起来。TtlRunnable.get(task)的get方法最后调到了:
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
原理:
1、TTL继承自InheritableThreadLocal。
2、通过一个Holder,保存了每个线程当前持有的所有ThreadLocal对象。
3、用TtlRunnable的get方法来包裹一个Runnable对象,包裹对象时,会采用类似SNAPSHOT,快照的机制,通过Holder,捕获父线程当前持有的所有ThreadLocal。随后,子线程启动,在Runnable对象执行run方法之前,从Holder中取出先前捕获到的父线程所持有的ThreadLocal对象,并设置到当前子线程当中,设置之前会保存子线程原有的ThreadLocal作为backUp,当子线程执行结束后,通过backUp恢复其原有的ThreadLocal。
3.3、线程池使用:
抽取公共ThreadLocal容器类:
public final class MyThreadLocalContext {
private MyThreadLocalContext() {}
private static final ThreadLocal<String> parent = new TransmittableThreadLocal<String>();
public static String get() {
return parent.get();
}
public static void set(String data) {
parent.set(data);
}
public static void remove() {
parent.remove();
}
}
子线程类和主线程通过公共容器类操作即可。
还有其他功能:
以上代码 下载地址