**高并发、微服务 、性能调优实战案例100讲，所有案例均源于个人工作实战，均配合代码落地**

加我微信：itsoku，所有案例均提供在线答疑。



# 第26节 ThreadLocal 遇到线程池有大坑 & 通用解决方案

<span style="font-weight:bold; color:red">目前整个课程59块钱，100个案例，含所有源码 & 文档 & 技术支持，可点击左下角小黄车了解</span>

## 本文内容

- 演示 ThreadLocal 遇到线程池出现的故障
- 演示 InheritableThreadLocal 遇到线程池出现的故障
- 通过自定义线程池解决这些问题，也是通用的一个方案，大家一定要掌握



## 涉及到的技术

- ThreadLocal：当前线程数据共享的一个工具类，java中自带的
- InheritableThreadLocal：父子线程数据共享的一个工具类，java中自带的
- 自定义线程池

上案例。



## 案例1：测试 ThreadLocal 遇到线程池会怎么样？

### 案例代码

```java
/**
 * 1、测试 ThreadLocal 遇到线程池会怎么样？
 *
 * @throws InterruptedException
 */
@Test
public void threadLocalTest() throws InterruptedException {
    //1、创建一个ThreadLocal，用来存放用户名
    ThreadLocal<String> userNameTl = new ThreadLocal<>();
    //2、当前线程，即主线程中，放入用户名:路人
    userNameTl.set("路人");
    //3、在当前线程中从ThreadLocal获取用户名
    this.log(userNameTl.get());

    //4、创建大小为2的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    //5、循环5次，通过线程池去执行任务，任务中去从 userNameTl 获取用户名，看看是否可以获取到？
    for (int i = 0; i < 5; i++) {
        executorService.execute(() -> {
            String userName = userNameTl.get();
            this.log(userName);
        });
    }

    //关闭线程池，并等待结束
    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.MINUTES);
}

private void log(String s) {
    System.out.println(Thread.currentThread().getName() + ":" + s);
}
```

### 运行输出

```
main:路人
pool-1-thread-1:null
pool-1-thread-2:null
pool-1-thread-2:null
pool-1-thread-2:null
pool-1-thread-2:null
```

### 结论

ThreadLocal 只能在当前线程共享数据， 在线程池中无法获取到外面线程中ThreadLocal中的数据。

### 疑问？

InheritableThreadLocal 可以实现父子线程共享数据，他能解决这个问题么？看案例2



## 案例2：测试 InheritableThreadLocal 遇到线程池会怎么样？

### 案例代码

```java
/**
 * 2、测试 InheritableThreadLocal 遇到线程池会怎么样？
 *
 * @throws InterruptedException
 */
@Test
public void InheritableThreadLocalTest() throws InterruptedException {
    //1、创建一个InheritableThreadLocal，用来存放用户名
    InheritableThreadLocal<String> userNameItl = new InheritableThreadLocal<>();

    //2、创建大小为2的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    //3、循环5次，通过线程池去执行任务，执行任务之前会生成一个用户名放入 userNameItl，然后在线程池中的任务中，将用户再取出来，看看和外面丢进去的是不是一样的？
    for (int i = 0; i < 5; i++) {
        //主线程中用户名，丢到userNameItl中
        String mainThreadUserName = "路人-" + i;
        userNameItl.set(mainThreadUserName);

        executorService.execute(() -> {
            //线程池中获取用户名
            String threadPoolThreadUserName = userNameItl.get();
            this.log(String.format("mainThreadUserName:" + mainThreadUserName + ",threadPoolThreadUserName:" + threadPoolThreadUserName));
        });

        TimeUnit.SECONDS.sleep(1);
    }

    //关闭线程池，并等待结束
    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.MINUTES);
}
```

### 运行输出

大家重点关注：mainThreadUserName的值和threadPoolThreadUserName的值是否一致，从输出中可以看出，前2行是一样的，后面3行不一样了。

```java
pool-1-thread-1:mainThreadUserName:路人-0,threadPoolThreadUserName:路人-0
pool-1-thread-2:mainThreadUserName:路人-1,threadPoolThreadUserName:路人-1
pool-1-thread-1:mainThreadUserName:路人-2,threadPoolThreadUserName:路人-0
pool-1-thread-2:mainThreadUserName:路人-3,threadPoolThreadUserName:路人-1
pool-1-thread-1:mainThreadUserName:路人-4,threadPoolThreadUserName:路人-0
```

## 原因

线程中的2个子线程是重复使用，他的父线程都是main线程，子线程创建的时候，会将父线程中InheritableThreadLocal中的数据复制到子线程中，后续父线程中InheritableThreadLocal的数据再变化的时候，对子线程是没有影响的，所以出现了上面的现象。

### 结论

InheritableThreadLocal 也无法解决外面线程和线程池中线程数据共享的问题。



## 如何解决这个难题？

我们可以通过自定义线程池解决，需要重写线程池的execute和submit方法，在线程池执行任务前，先将需要在线程池中共享的数据取出来，然后在执行任务的时候，再将这个数据塞到线程池执行任务的线程中就可以了。

案例3将对这个方案进行落地。



## 案例3：自定义线程池解决这个问题

### 案例代码

大家先不要取研究代码，我们先看效果，稍后给大家解释源码

```java
/**
 * 3、自定义线程池解决这个问题
 */
@Test
public void threadLocalTestNew() throws InterruptedException {
    //1、创建一个ThreadLocal，用来存放用户名
    ThreadLocal<String> userNameTl = new ThreadLocal<>();

    //2、创建大小为2的线程池，大家先不用过度关注这块代码，稍后会解释
    ExecutorService executorService = new MyThreadPoolExecutor(new MyThreadPoolExecutor.ThreadLocalContext<String>() {
        @Override
        public String getContext() {
            return userNameTl.get();
        }

        @Override
        public void setContext(String userName) {
            userNameTl.set(userName);
        }
    }, 2, 2, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<>(100));
    
    //3、循环5次，通过线程池去执行任务，执行任务之前会生成一个用户名放入 userNameTl，然后在线程池中的任务中，将用户再取出来，看看和外面丢进去的是不是一样的？
    for (int i = 0; i < 5; i++) {
        //主线程中用户名，丢到userNameTl中
        String mainThreadUserName = "路人-" + i;
        userNameTl.set(mainThreadUserName);

        executorService.execute(() -> {
            //线程池中获取用户名
            String threadPoolThreadUserName = userNameTl.get();
            this.log(String.format("mainThreadUserName:" + mainThreadUserName + ",threadPoolThreadUserName:" + threadPoolThreadUserName));
        });

        TimeUnit.SECONDS.sleep(1);
    }

    //关闭线程池，并等待结束
    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.MINUTES);
}
```

### 运行输出

可以看下，mainThreadUserName和threadPoolThreadUserName的值都是一致的，说明当前这个案例解决了线程池共享外部ThreadLocal数据的问题。

```java
pool-1-thread-1:mainThreadUserName:路人-0,threadPoolThreadUserName:路人-0
pool-1-thread-2:mainThreadUserName:路人-1,threadPoolThreadUserName:路人-1
pool-1-thread-1:mainThreadUserName:路人-2,threadPoolThreadUserName:路人-2
pool-1-thread-2:mainThreadUserName:路人-3,threadPoolThreadUserName:路人-3
pool-1-thread-1:mainThreadUserName:路人-4,threadPoolThreadUserName:路人-4
```

### 源码解析

重点在于下面这个自定义线程池。

```java
import java.util.concurrent.*;

/**
 * <b>description</b>： Java高并发、微服务、性能优化实战案例100讲，视频号：程序员路人，源码 & 文档 & 技术支持，请加个人微信号：itsoku <br>
 * <b>time</b>：2024/4/24 13:27 <br>
 * <b>author</b>：ready likun_557@163.com
 */
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
    private ThreadLocalContext threadLocalContext;

    public MyThreadPoolExecutor(ThreadLocalContext context, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.threadLocalContext = context;
    }

    public MyThreadPoolExecutor(ThreadLocalContext context, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        this.threadLocalContext = context;
    }

    @Override
    public void execute(Runnable command) {
        super.execute(this.new RunnableWrap(command));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(task);
    }

    /**
     * 线程本地变量上下文接口，用于解决线程池中共享外部线程ThreadLocal数据的问题
     *
     * @param <T>
     */
    public interface ThreadLocalContext<T> {
        /**
         * 获取线程池中需要共享的上下文对象，将任务交给线程时会被调用
         *
         * @return
         */
        T getContext();

        /**
         * 设置上下文，线程池中的线程执行任务的时候会调用
         *
         * @param context {@link #getContext()} 返回的对象
         */
        void setContext(T context);
    }

    private class CallableWrap<V> implements Callable<V> {

        private Callable<V> target;

        private Object context;

        public CallableWrap(Callable<V> target) {
            this.target = target;
            this.context = MyThreadPoolExecutor.this.threadLocalContext.getContext();
        }

        @Override
        public V call() throws Exception {
            MyThreadPoolExecutor.this.threadLocalContext.setContext(this.context);
            return this.target.call();
        }
    }


    private class RunnableWrap implements Runnable {

        private Runnable target;

        private Object context;

        public RunnableWrap(Runnable target) {
            this.target = target;
            this.context = MyThreadPoolExecutor.this.threadLocalContext.getContext();
        }

        @Override
        public void run() {
            MyThreadPoolExecutor.this.threadLocalContext.setContext(this.context);
            this.target.run();
        }
    }
}
```



## 扩展

SpringBoot中有个@Async注解，标注在方法上，可以让这个方法异步执行，如果刚好用到了ThreadLocal来共享数据，那么就可能碰到本文中的问题。

比如：调用@Async标注的方法之前，向ThreadLocal中放入了数据，然后在@Async标注的方法中，通过ThreadLocal却取不到数据，这个时候大家应该知道如何解决了吧。



## 案例源码

源码同样是放在我的《高并发&微服务&性能调优实战案例100讲》的代码中（lesson026模块中），有兴趣的可以点击左下角的小黄车了解下，感谢大家的观看。



# 高并发 & 微服务 & 性能调优实战案例100讲

## 已更新 26 节课

<span style="font-weight:bold; color:red">目前整个课程59块钱，含所有源码 & 文档 & 技术支持，一杯咖啡的价格，还没下手的朋友，赶紧了，马上要涨价了</span>。

```java
1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
```



## 课程部分大纲，连载中。。。。

以下课程均来源于个人多年的实战，均提供原理讲解 && 源码落地

1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
27. SpringBoot读写分离实战
28. MQ专题：事务消息实战（防止消息丢失）
29. MQ专题：消息消息重试策略
30. MQ专题：消息幂等消费通用方案实战
31. MQ专题：延迟消息通用方案实战
32. MQ专题：顺序消息通用方案实战
33. MQ专题：消息积压问题
34. 分布式事务：事务消息实现事务最终一致性
35. 分布式事务：通用的TCC分布式事务生产级代码落地实战
36. 分布式锁案例实战
37. 微服务中如何传递上下文？实战
38. 微服务链路日志追踪实战（原理&代码落地）
39. SpringBoot实现租户数据隔离
40. MyBatis进阶：封装MyBatis，实现通用的无SQL版CRUD功能，架构师必备
41. MyBatis进阶：自己实现通用分表功能，架构师必备
42. MyBatis进阶：实现多租户隔离ORM框架
43. SpringBoot中实现自动监听PO的变化，自动生成表结构
44. 分布式专题：其他实战课程等
45. 性能调优：如何排查死锁？
46. 性能调优：如何排查内存溢出？
47. 性能调优：CPU被打满，如何排查？
48. 性能调优：生产代码没生效，如何定位？
49. 性能调优：接口太慢，如何定位？
50. 性能调优：如何查看生产上接口的入参和返回值？
51. 性能调优：远程debug
52. 生产上出现了各种故障，如何定位？
53. db和缓存一致性，常见的方案
54. Redis场景案例。。。
55. 系统资金账户设计案例（一些系统涉及到资金操作）
56. 其他等各种实战案例。。。