前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你真的会用ThreadLocal吗

你真的会用ThreadLocal吗

作者头像
程序员小义
发布2024-04-10 15:59:16
700
发布2024-04-10 15:59:16
举报
文章被收录于专栏:小义思

先来解释一下什么是ThreadLocal?

一句话理解,threadlocal是作为当前线程中属性ThreadLocalMap集合的某一个Entry的key值(Entry的key是ThreadLocal,value是要存储的副本变量),不同的线程所拥有的ThreadLocalMap是互相隔离的。

ThreadLocal为什么建议用static修饰?

static修饰的变量是在类在加载时就分配地址了,在类卸载才会被回收,如果变量ThreadLocal是非static的话就会造成每次生成实例都要生成不同的ThreadLocal对象,虽然这样程序虽然不会有什么异常,但是会浪费内存资源。

什么场景适合使用ThreadLocal呢?

当每个线程需要有自己单独的变量副本,或者说变量需要在多个方法中共享但不希望被多线程共享的时候,就适合使用threadlocal。例如用threadlocal来保存当前用户的登录信息。

ThreadLocal的传递性如何实现?

对于业务系统来说,用户登录了之后,后端可以通过拦截器将用户信息这一变量存在threadlocal中。但是在使用线程池时,其他线程的threadlocal在不重新赋值的情况下就取不到用户信息。怎么实现父子线程之间这一变量的传递呢?难道只能通过参数传值吗?有没有更加优雅的方式呢?这就要涉及JDK的InheritableThreadLocal和阿里巴巴的TransmittableThreadLocal。 InheritableThreadLocal可以实现子线程继承父线程的threadlocal,但是有坑,在线程池中因为线程的复用性,子线程就无法有效继承。而使用TransmittableThreadLocal就很好的解决这一问题,注意要引入以下依赖包。

代码语言:javascript
复制
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.12.0</version>
        </dependency>

代码验证

  1. InheritableThreadLocal 的父子线程传递性
代码语言:javascript
复制
public class ThreadLocalTest {
    private static ThreadLocal<UserVO> userThreadLocal = new ThreadLocal<>();
    private static ThreadLocal<UserVO> inheritableuserThreadLocal = new InheritableThreadLocal<>();
    private static ThreadLocal<UserVO> ttluserThreadLocal = new TransmittableThreadLocal<>();

    /**
     * 验证 InheritableThreadLocal 的父子线程传递性
     */
    @Test
    public void inheritableThreadTest() throws InterruptedException {
        inheritableuserThreadLocal.set(new UserVO().setName("main-T"));
        System.out.println("M:"+inheritableuserThreadLocal.get());
        Thread thread = new Thread(() -> {
            System.out.println("S:"+inheritableuserThreadLocal.get());
            inheritableuserThreadLocal.set(new UserVO().setName("child").setAge("20"));
            //inheritableuserThreadLocal.get().setName("child").setAge("20");
            System.out.println("S:"+inheritableuserThreadLocal.get());
            inheritableuserThreadLocal.remove();
        });
        thread.start();
        Thread.sleep(5000);
        System.out.println("M:"+inheritableuserThreadLocal.get());
        inheritableuserThreadLocal.remove();
    }
    /** 打印结果 */
//        M:UserVO(name=main-T, age=null)
//        S:UserVO(name=main-T, age=null)
//        S:UserVO(name=child, age=20)
//        M:UserVO(name=main-T, age=null)
}

从打印结果可以得知,当另起线程时,inheritableuserThreadLocal是可以实现继承性的。但是注意子线程继承的对象是浅拷贝,如果放开代码中的注释行,也就是修改变量值,那么父线程中的threadlocal也会修改,因为两者引用指向的是同一个对象。

  1. 线程池中InheritableThreadLocal失效
代码语言:javascript
复制
    /**
     * 验证 InheritableThreadLocal 在线程池中的效果
     */
    @Test
    public void inheritableThreadPoolTest() throws InterruptedException {
        inheritableuserThreadLocal.set(new UserVO().setName("mainUser"));
        System.out.println("M:"+inheritableuserThreadLocal.get());
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000));
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(() -> {
                System.out.println("S:"+inheritableuserThreadLocal.get());
                inheritableuserThreadLocal.remove();
            });
        }
        Thread.sleep(5000);
        inheritableuserThreadLocal.remove();
    }
    /** 打印结果 */
//    M:UserVO(name=mainUser, age=null)
//    S:UserVO(name=mainUser, age=null)
//    S:null
//    S:null
//    S:null
//    S:UserVO(name=mainUser, age=null)

之所以有的线程会打印出null值,是因为在使用InheritableThreadLocal时父线程的ThreadLocalMap是通过实例化一个Thread时赋值给子线程的,但是在线程池中业务线程只是将任务(实现了Runnable或者Callable的对象)加入到任务队列中,并不一定去创建线程池中的线程,因此线程池中线程也就获取不到业务线程中的上下文信息。

  1. 阿里开源的TransmittableThreadLocal

参考文档:https://github.com/alibaba/transmittable-thread-local

代码语言:javascript
复制
    /**
     * 验证TransmittableThreadLocal(错误用法)
     */
    @Test
    public void ttlThreadPoolErrorTest() throws InterruptedException {
        ttluserThreadLocal.set(new UserVO().setName("hello"));
        System.out.println("M:"+ttluserThreadLocal.get());
        for (int i = 0; i < 10; i++) {
            ThreadUtil.execAsync(() -> {
                System.out.println("S:"+ttluserThreadLocal.get());
                ttluserThreadLocal.remove();
            });
        }
        Thread.sleep(5000);
        ttluserThreadLocal.remove();
    }
    /** 打印结果 */
//    M:UserVO(name=hello, age=null)
//    S:UserVO(name=hello, age=null)
//    S:UserVO(name=hello, age=null)
//    S:UserVO(name=hello, age=null)
//    S:UserVO(name=hello, age=null)
//    S:null
//    S:null
//    S:null
//    S:null
//    S:UserVO(name=hello, age=null)

    /**
     * TransmittableThreadLocal的正确用法
     */
    @Test
    public void ttlThreadPoolCorrectTest() throws InterruptedException {
        ttluserThreadLocal.set(new UserVO().setName("m").setAge("99"));
        System.out.println("M:"+ttluserThreadLocal.get());
        //需用TtlExecutors包装一层才能正常使用
        ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(ThreadUtil.newExecutor(2,4));
        for (int i = 0; i < 10; i++) {
            ttlExecutorService.submit(() -> {
                System.out.println("S:" + ttluserThreadLocal.get());
            });
        }
        Thread.sleep(5000);
        ttluserThreadLocal.remove();
    }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员小义 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先来解释一下什么是ThreadLocal?
  • ThreadLocal为什么建议用static修饰?
  • 什么场景适合使用ThreadLocal呢?
  • ThreadLocal的传递性如何实现?
  • 代码验证
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档