ThreadLocal、InheritableThreadLocal、transmittablethreadlocal简单记录

本文详细介绍了ThreadLocal的作用,如何避免线程间数据竞争,以及InheritableThreadLocal如何实现跨线程继承。通过实例演示了如何在多线程环境下使用这两种技术,并探讨了它们在避免数据混乱和线程池配合中的应用场景。

ThreadLocal

ThreadLocal用于保存线程线程本地变量,访问这个变量的每个线程都会有这个变量的一个本地副本,多个线程同时对这个变量进行读写操时,实际上操作的是线程自己本地内存中的变量,从而避免了线程安全的问题。

比如多个方法都用到某个参数,为了避免一直传递,可以放到ThreadLocal中。

//    threadLocal :主线程的threadLocal不会往子线程复制的,只是保留了一个空副本
    public static  final ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<>();
    static {
        Map<String, Object> map = new ConcurrentHashMap<>();
        map.put("cnt",0);
        threadLocal.set(map);
    }
    public static void main(String[] args) {
        System.out.println(threadLocal.get().get("cnt"));
        CountDownLatch countDownLatch = new CountDownLatch(9);
        for (int i = 1;i<10;i++){
            int finalI = i;
            try {
                new Thread(new Runnable() {
                    public void run() {
                        //threadLocal 线程隔离这里会报空指针异常,所以一般我们都会做一个get方法,如果是空则付一下默认值
                        System.out.println(threadLocal.get().get("cnt"));
                    }
                }).start();

            }catch   (Exception e) {
                throw new RuntimeException("111");
            } finally {
                countDownLatch.countDown();
            }
        }
        try {
            countDownLatch.await(); //保证之前的所有的线程都执行完成,才会走下面的;
        } catch (Exception e) {
            throw new RuntimeException();
        }

    }

InheritableThreadLocal

能够使让子线程访问到父线程中设置的本地变量的值

//    InheritableThreadLocal 可以向子线程传递,这个复制是引用复制,即子线程的修改会反馈到主线程
    public static  final InheritableThreadLocal<Map<String,Object>> threadLocal = new InheritableThreadLocal<>();
    static {
        Map<String, Object> map = new ConcurrentHashMap<>();
        map.put("cnt",0);
        threadLocal.set(map);
    }
    public static void main(String[] args) throws InterruptedException {
        System.out.println("最初:"+threadLocal.get().get("cnt"));
        CountDownLatch countDownLatch = new CountDownLatch(9);
        for (int i = 1;i<10;i++){
            int finalI = i;
                if (i==7){
                    Thread.sleep(i*100);
                }
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            Map<String, Object> subMap = threadLocal.get();
                            System.out.println(Thread.currentThread().getName()+"加之前:"+threadLocal.get().get("cnt"));
                            subMap.put("cnt",Integer.parseInt(subMap.get("cnt").toString())+finalI);
                            threadLocal.set(subMap);
                            System.out.println(Thread.currentThread().getName()+"加之后:"+threadLocal.get().get("cnt"));
                        }catch   (Exception e) {
                            throw new RuntimeException("111");
                        } finally {
                            countDownLatch.countDown();
                        }
                    }
                }).start();

        }
        try {
            countDownLatch.await(); //保证之前的所有的线程都执行完成,才会走下面的;
        } catch (Exception e) {
            throw new RuntimeException();
        }
        System.out.println("最终:"+threadLocal.get().get("cnt"));
    }

InheritableThreadLocal可以保证子线程获得主线程的变量,其实是在线程创建的时候从主线程中对线程变量做了一个地址引用,所以说在子线程结束之后,主线程的线程变量也改变了。

这里需要考虑下,如果使用得当线程池,核心线程创建之后是不会销毁的,所以此变量就一直保留了下来。

阿里的transmittablethreadlocal  可以解决InheritableThreadLocal 和线程池搭配,核心线程不销毁,导致的InheritableThreadLocal 错乱问题。

TransmittableThreadLocal详解_邋遢的流浪剑客的博客-优快云博客_transmittable-thread-local这个老哥写的挺详细的。

### Spring Boot 中 `ThreadLocal` 的使用方法 在Spring Boot应用程序中,`ThreadLocal` 是一种用于在线程之间隔离变量的技术。这使得每个线程都有自己的局部变量副本,从而避免了并发访问带来的同步问题。 #### 基本概念 `ThreadLocal` 提供了一种机制,在不依赖于全局或共享状态的情况下保存特定于调用线程的数据[^1]。这意味着即使多个请求由同一个物理线程处理,它们仍然可以拥有独立的状态信息而不互相干扰。 #### 实现方式 为了实现这一点,通常会在服务层定义一个静态成员变量作为 `ThreadLocal` 容器: ```java private static final ThreadLocal<Integer> contextHolder = new ThreadLocal<>(); ``` 当需要设置当前线程特有的值时,可以通过如下方式进行赋值: ```java contextHolder.set(someValue); ``` 而获取该线程特有值则简单地调用 `.get()` 方法即可: ```java Integer value = contextHolder.get(); ``` 最后不要忘记清理资源以防止内存泄漏: ```java contextHolder.remove(); ``` 这种模式特别适用于像用户认证信息这样的场景,其中每个HTTP请求都应该携带其自身的会话数据而不是与其他请求混淆在一起[^4]。 #### 多线程环境下的注意事项 然而需要注意的是,默认情况下子线程不会继承父线程的 `ThreadLocal` 变量;如果确实有这样的需求,则应该考虑使用 `InheritableThreadLocal` 或者更高级别的解决方案如阿里巴巴开源的 `TransmittableThreadLocal` 来传递上下文信息给新创建的工作线程[^3]。 #### 日志记录与性能监控 除了上述提到的应用之外,`ThreadLocal` 还常被用来做日志追踪以及性能指标收集等工作。例如可以在过滤器/拦截器处初始化一些唯一标识符并将其存入到 `ThreadLocal` 中以便后续的日志输出能够带上这些额外的信息[^2]。 ### 示例代码展示如何利用 `ThreadLocal` 存储用户电话号码 下面是一个简单的例子展示了怎样在一个基于Spring Boot构建的服务里边通过 `ThreadLocal` 来保持用户的手机号码,并且提供了一个工具类方便其他组件查询这个信息: ```java // UserContextHolder.java public class UserContextHolder { private static final ThreadLocal<String> userPhoneHolder = new ThreadLocal<>(); public static void setUserPhone(String phone){ userPhoneHolder.set(phone); } public static String getUserPhone(){ return userPhoneHolder.get(); } public static void clearUserPhone(){ userPhoneHolder.remove(); } } // SomeService.java @Service public class SomeService { /** * 获取手机号 * * @return 手机号 */ private String getPhone() { String phone; try { phone = UserContextHolder.getUserPhone(); } catch (Exception e) { phone = "no login"; } return phone; } } ``` 此段代码片段说明了如何封装对 `ThreadLocal` 的操作逻辑,使其更加易于重用和测试。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值