1. 正确的ThreadLocal的使用
1.1 如何使用
在同一个线程中,通过ThreadLocal传递上下文,在使用后进行释放
1.2 使用场景
-
示意图
- 示例说明
- 如果web应用在处理tom的请求的时候,bob又进来,使用了线程t2, t1和t2的threadlocal变量是隔离的,也就是线程安全的
- 线程使用完后将threadlocal释放,避免内存泄漏
- 这种情况下,线程的thradlocal可以从上层流转到下层,上层的修改对下层是可见的
1.3 代码示例
package com.zihao.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author tangzihao
* @date 2020/9/5 8:08 下午
*/
@RestController
public class TestThreadLocalController {
private static final ThreadLocal threadLocal = new ThreadLocal<>();
/**
* 处理用户的请求
* @param uid 用户的uid
*/
@RequestMapping("/testThreadLocal")
public void handleRequest(String uid){
System.out.println("current user uid:"+uid);
//将uid放入ThreadLocal中,通过ThreadLocal进行传值,避免共享成员变量产生线程不安全
threadLocal.set(uid);
query();
}
/**
* 查询用户信息
*/
private void query(){
try{
queryCache();
queryDb();
}finally {
//用完后一定要释放该线程的threadLocal
threadLocal.remove();
//移除后输出下内容
System.out.println("after remove,current threadLocal:"+threadLocal.get());
System.out.println();
}
}
/**
* 模拟查询缓存
*/
private void queryCache(){
System.out.println("thread ["+Thread.currentThread().getName()+"] query cache, uid:"+threadLocal.get());
}
/**
* 模拟查询数据库
*/
private void queryDb(){
System.out.println("thread ["+Thread.currentThread().getName()+"] query db, uid:"+threadLocal.get());
}
}
我是在springboot项目中,简单写了个controller模拟用户的请求
1.4 输出结果
1.5 其他联想
- tomcat的线程池的设计策略
- tomcat的线程池是如何扩展jdk的线程池的,具体见tomcat源码的org.apache.tomcat.util.threads.ThreadPoolExecutor,tomcat源码编译本博客有可以看下喔
- springboot内嵌的tomcat配置
- 配置项见ServerProperties
- Spring boot如何切换不同的web容器
- 使用不同的web容器,线程池的命名是不同的
- 容器可以切换成jetty和undertow
- 排除spring-boot-starter-web的spring-boot-starter-tomcat
- 引入spring-boot-starter-jetty或者spring-boot-starter-undertow
2. 父子线程传值的问题
2.1 示例代码
package com.test.zihao;
/**
* 父子线程传递ThreadLocal变量
*
* @author tangzihao
* @date 2020/9/5 9:42 下午
*/
public class ParentChildThreadLocalCase {
private static final ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//main线程 设置threadLocal
threadLocal.set("hello");
Thread childThread = new Thread(() -> {
System.out.println("current thread: " + Thread.currentThread().getName() + ", value from thread local: " + threadLocal.get());
}, "childThread");
childThread.start();
System.out.println(Thread.currentThread().getName() + ", value from thread local: " + threadLocal.get());
}
}
2.2 输出结果
main, value from thread local: hello
current thread: childThread, value from thread local: null
2.3 原因分析
- ThreadLocal是对当前线程有效
- 这个case中有两个线程
- 线程1,main线程,main程序的执行线程,main线程设置了自己线程的threadLocal,只有main线程自己get才可以获取到之前它自己设置的值
- 线程2,childThread线程,是main线程的子线程,子线程尝试获取父线程main的threadlocal变量,获取为null
3. InheritableThreadLocal解决父子线程的传值
3.1 示例代码
package com.test.zihao;
/**
* 父子线程传递ThreadLocal变量
*
* @author tangzihao
* @date 2020/9/5 9:42 下午
*/
public class ParentChildThreadLocalCase {
private static final InheritableThreadLocal threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
//main线程 设置threadLocal
threadLocal.set("hello");
Thread childThread = new Thread(() -> {
System.out.println("current thread: " + Thread.currentThread().getName() + ", value from thread local: " + threadLocal.get());
}, "childThread");
childThread.start();
System.out.println(Thread.currentThread().getName() + ", value from thread local: " + threadLocal.get());
}
}
3.2 输出结果
current thread: childThread, value from thread local: hello
main, value from thread local: hello
3.3 结论
- 使用InheritableThreadLocal可以在父子线程之间传递ThreadLocal变量
- 子线程修改的InheritableThreadLocal对父线程不可见
- 子线程之间的InheritableThreadLocal彼此不可见
3.4 InheritableThreadLocal传值的源码
(1) 如果父线程的inheritableThreadLocals不为null同时允许继承inheritThreadLocals的话,将父线程的inheritableThreadLocals复制到子线程
Thread源码的418行
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
(2) InheritableThreadLocal通过重写childValue方法来将父线程的值注入
ThreadLocal的ThreadLocalMap(ThreadLocalMap parentMap)方法
private ThreadLocalMap(ThreadLocalMap parentMap) {
//构造table数组
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//复制parentMap的entry
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
InheritableThreadLocal line61
protected T childValue(T parentValue) {
return parentValue;
}
(3) 通过重写getMap方法,将threadLocal的get和set方法由线程的inheritableThreadLocals维护
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
4. 线程池复用的传值问题
4.1 问题简单描述
对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。
4.2 使用场景
- 假设我有个需求需要记录程序执行的方法调用顺序,在方法调用中需要使用线程池进行并发调用
-
执行示意图
4.3 问题代码
package com.test.zihao;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池使用threadLocal
*
* @author tangzihao
* @date 2020/9/5 10:40 下午
*/
public class ThreadPoolUseInheritableThreadLocalCase {
private static final InheritableThreadLocal threadLocal = new InheritableThreadLocal<>();
private static ExecutorService executorService = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadLocal.set("main-->");
executorService.submit(ThreadPoolUseInheritableThreadLocalCase::queryShop);
executorService.submit(ThreadPoolUseInheritableThreadLocalCase::queryItem);
executorService.submit(ThreadPoolUseInheritableThreadLocalCase::queryCoupon);
}
/**
* 查询店铺信息
*/
private static void queryShop() {
threadLocal.set(threadLocal.get() + "queryShop");
record();
}
/**
* 查询商品
*/
private static void queryItem() {
threadLocal.set(threadLocal.get() + "queryItem");
record();
}
/**
* 查询优惠券
*/
private static void queryCoupon() {
threadLocal.set(threadLocal.get() + "queryCoupon");
record();
}
/**
* 记录日志
*/
private static void record() {
threadLocal.set(threadLocal.get() + "-->record");
System.out.println(Thread.currentThread().getName() + " method call chain[ " + threadLocal.get() + " ]");
}
}
4.4 输出结果
明显的发现在复用pool-1-thread-1的时候,无法获取提交线程池任务时候的threadlocal值,而是“残留”了上一次使用记录的方法调用信息
pool-1-thread-1 method call chain[ main-->queryShop-->record ]
pool-1-thread-2 method call chain[ main-->queryItem-->record ]
pool-1-thread-1 method call chain[ main-->queryShop-->recordqueryCoupon-->record ]
4.5 结论
我们需要一种机制把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。
5. 使用TransmittableThreadLocal实现线程池复用的传值
5.1 修饰Runnable
- 用法简介
- 获取原生的实现runnable的task
- 使用TtlRunnable修饰原生的runnable
- 代码示例
package com.test.zihao;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TtlRunnableTest {
private static final CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
TransmittableThreadLocal parent = new TransmittableThreadLocal();
parent.set("value-set-in-parent");
ConcurrentHashMap> ttlInstance =
new ConcurrentHashMap>();
ttlInstance.put("1",parent);
Runnable task = new MyTask("thread1","1",ttlInstance);
Runnable task1 = new MyTask("thread2","1",ttlInstance);
Runnable ttlRunnable = TtlRunnable.get(task);
Runnable ttlRunnable1 = TtlRunnable.get(task1);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(ttlRunnable);
executorService.submit(ttlRunnable1);
//等待子线程全部完成后 尝试从main线程获取对应的TransmittableThreadLocal
latch.await();
System.out.println("after task and task1 finished, parent : "+ttlInstance.get("1").get());
executorService.shutdown();
}
static class MyTask implements Runnable{
private ConcurrentHashMap> ttlInstance;
private String tag;
public MyTask(String name,String tag, ConcurrentHashMap> ttlInstance){
Thread.currentThread().setName(name);
this.tag = tag;
this.ttlInstance = ttlInstance;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" child before set: "+ttlInstance.get("1").get());
ttlInstance.get("1").set("value-set-in-child"+new Random().nextInt(10));
System.out.println(Thread.currentThread().getName()+" child after set: "+ttlInstance.get("1").get());
latch.countDown();
}
}
}
- 代码输出
pool-1-thread-1 child before set: value-set-in-parent
pool-1-thread-2 child before set: value-set-in-parent
pool-1-thread-1 child after set: value-set-in-child4
pool-1-thread-2 child after set: value-set-in-child1
after task and task1 finished, parent : value-set-in-parent
5.2 修饰Callable
-
用法简介
- 获取原生的实现callable的task
- 使用TtlCallable修饰原生的callable
代码示例
package com.test.zihao;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlCallable;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author tangzihao
* @date 2020/9/6 9:34 下午
*/
public class TtlCallableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
TransmittableThreadLocal parent = new TransmittableThreadLocal();
parent.set("value-set-in-parent");
ConcurrentHashMap> ttlInstance =
new ConcurrentHashMap>();
ttlInstance.put("1", parent);
Callable task = new MyTask("thread1", "1", ttlInstance);
Callable task1 = new MyTask("thread2", "1", ttlInstance);
Callable ttlCallable = TtlCallable.get(task);
Callable ttlCallable1 = TtlCallable.get(task1);
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future future = executorService.submit(ttlCallable);
Future future1 = executorService.submit(ttlCallable1);
System.out.println("thread1 after set: " + future.get());
System.out.println("thread2 after set: " + future1.get());
System.out.println("after task and task1 finished, parent : " + ttlInstance.get("1").get());
executorService.shutdown();
}
static class MyTask implements Callable {
private ConcurrentHashMap> ttlInstance;
private String tag;
public MyTask(String name, String tag, ConcurrentHashMap> ttlInstance) {
Thread.currentThread().setName(name);
this.tag = tag;
this.ttlInstance = ttlInstance;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + " child before set: " + ttlInstance.get("1").get());
ttlInstance.get("1").set("value-set-in-child" + new Random().nextInt(10));
return ttlInstance.get("1").get();
}
}
}
- 代码输出
pool-1-thread-1 child before set: value-set-in-parent
pool-1-thread-2 child before set: value-set-in-parent
thread1 after set: value-set-in-child9
thread2 after set: value-set-in-child4
after task and task1 finished, parent : value-set-in-parent
5.3 修饰线程池
- 使用方法
- 使用原生的ExecutorService
- 使用TtlExecutors包装ExecutorService
- 代码示例
package com.test.zihao;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 调用链
* |----queryShop----record
* main---|----queryItem----record
* |----queryCoupon---record
* @author tangzihao
* @date 2020/9/5 11:17 下午
*/
public class TtlExecutorTest {
private static final TransmittableThreadLocal threadLocal = new TransmittableThreadLocal<>();
private static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
public static void main(String[] args) {
threadLocal.set("main-->");
executorService.submit(TtlExecutorTest::queryShop);
executorService.submit(TtlExecutorTest::queryItem);
executorService.submit(TtlExecutorTest::queryCoupon);
}
/**
* 查询店铺信息
*/
private static void queryShop() {
threadLocal.set(threadLocal.get() + "queryShop");
record();
}
/**
* 查询商品
*/
private static void queryItem() {
threadLocal.set(threadLocal.get() + "queryItem");
record();
}
/**
* 查询优惠券
*/
private static void queryCoupon() {
threadLocal.set(threadLocal.get() + "queryCoupon");
record();
}
/**
* 记录日志
*/
private static void record() {
threadLocal.set(threadLocal.get() + "-->record");
System.out.println(Thread.currentThread().getName() + " method call chain[ " + threadLocal.get() + " ]");
}
}
- 输出
pool-1-thread-1 method call chain[ main-->queryShop-->record ]
pool-1-thread-2 method call chain[ main-->queryItem-->record ]
pool-1-thread-1 method call chain[ main-->queryCoupon-->record ]
*其他
- 可以看下Guava的ListeningExecutorService的线程池的包装来实现异步回调
//创建Java线程池
ExecutorService jPool = Executors.newFixedThreadPool(10);
//包装Java线程池,构造Guava线程池
ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool);