了解该库之前,建议先看下:
源码地址: https://github.com/alibaba/transmittable-thread-local。
可以简称为 TTL 。
maven 仓库地址: https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local
这个库解决了什么问题 ? ThreadLocal、InheritableThreadLocal 无法直接将值传播到新线程/线程池, 而该库可以解决这个问题。
引入依赖
gradle:
compile group: 'com.alibaba', name: 'transmittable-thread-local', version: '2.10.2'
maven:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.10.2</version>
</dependency>
示例1:基本使用
import com.alibaba.ttl.TransmittableThreadLocal;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) {
showLogId(); // null
logId.set(10);
showLogId(); // 10
logId.set(20);
showLogId(); // 20
logId.remove(); // 清除
showLogId(); // null
}
}
运行结果:
main : null
main : 10
main : 20
main : null
示例2:在多线程中使用
import com.alibaba.ttl.TransmittableThreadLocal;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId(); // null
logId.set(10);
showLogId(); // 10
Thread t1 = new Thread(()->{
showLogId(); // 10
logId.set(20);
showLogId(); // 20
});
Thread t2 = new Thread(()->{
showLogId(); // 10
logId.set(30);
showLogId(); // 30
});
t1.start(); // 运行线程 t1
t2.start(); // 运行线程 t2
t1.join(); // 等待线程 t1 执行完
t2.join(); // 等待线程 t2 执行完
showLogId(); // 10
}
}
执行结果:
main : null
main : 10
Thread-1 : 10
Thread-1 : 20
Thread-2 : 10
Thread-2 : 30
main : 10
可以看到 Thread-1 和 Thread-2 中的 logId的初始值都是 10,符合预期。
示例3:线程池错误的使用方式
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId(); // null
logId.set(10);
showLogId(); // 10
// 只有一个线程的线程池,该线程会复用
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 提交task
executorService.submit(() -> {
showLogId();
logId.set(20);
showLogId();
});
// 再提交一个task
executorService.submit(() -> {
showLogId();
logId.set(30);
showLogId();
});
// 关闭线程池
executorService.shutdown();
// 等待至任务都执行完,最多等3秒
executorService.awaitTermination(3, TimeUnit.SECONDS);
showLogId();
}
}
输出:
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 20
pool-1-thread-1 : 30
main : 10
可以看到 logId 还是被线程池里的logId复用了。如果要 logId 不被复用,那么就要用 TTL 系统的包装类。
示例4:线程池下的使用方式 - 包装 Runnale
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId(); // null
logId.set(10);
showLogId(); // 10
// 只有一个线程的线程池,该线程会复用
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 提交task
executorService.submit(TtlRunnable.get(() -> {
showLogId();
logId.set(20);
showLogId();
}));
// 再提交一个task
executorService.submit(TtlRunnable.get(() -> {
showLogId();
logId.set(30);
showLogId();
}));
// 关闭线程池
executorService.shutdown();
// 等待至任务都执行完,最多等3秒
executorService.awaitTermination(3, TimeUnit.SECONDS);
showLogId();
}
}
运行结果:
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 10
pool-1-thread-1 : 30
main : 10
可以看到,两个 task 虽然都在同一个线程中依次执行,但是 logId 的初始值都是 10,符合预期。
示例5:线程池下的使用方式 - 包装线程池
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId(); // null
logId.set(10);
showLogId(); // 10
// 使用包装的线程池
ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
// 提交task
executorService.submit(() -> {
showLogId();
logId.set(20);
showLogId();
});
// 再提交一个task
executorService.submit(() -> {
showLogId();
logId.set(30);
showLogId();
});
// 关闭线程池
executorService.shutdown();
// 等待至任务都执行完,最多等3秒
executorService.awaitTermination(3, TimeUnit.SECONDS);
showLogId();
}
}
运行结果:
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 10
pool-1-thread-1 : 30
main : 10
可以看到,两个 task 虽然都在同一个线程中依次执行,但是 logId 的初始值都是 10,符合预期。
示例6: 使用 javaagent
使用 javaagent,就不用包装 Runnable 或者线程池了。但执行时需要指定 javaagent。
在 maven 仓库中下载 ttl 的jar包到本地。
代码如下:
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TtlTest {
public static TransmittableThreadLocal<Integer> logId = new TransmittableThreadLocal<>();
// 输出线程名、logId在当前线程的值
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId(); // null
logId.set(10);
showLogId(); // 10
// 只有一个线程的线程池,该线程会复用
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 提交task
executorService.submit(() -> {
showLogId();
logId.set(20);
showLogId();
});
// 再提交一个task
executorService.submit(() -> {
showLogId();
logId.set(30);
showLogId();
});
// 关闭线程池
executorService.shutdown();
// 等待至任务都执行完,最多等3秒
executorService.awaitTermination(3, TimeUnit.SECONDS);
showLogId();
}
}
编译上面的源代码:
$ javac -cp ./transmittable-thread-local-2.10.2.jar TtlTest.java
-cp
换成 -classpath
也行。
运行时不指定 javaagent :
$ java -cp ./transmittable-thread-local-2.10.2.jar:. TtlTest
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 20
pool-1-thread-1 : 30
main : 10
结果不符合预期。
运行时指定 javaagent:
$ java -cp ./transmittable-thread-local-2.10.2.jar:. -javaagent:./transmittable-thread-local-2.10.2.jar TtlTest
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 10
pool-1-thread-1 : 30
main : 10
结果符合预期。