V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jiangxinlingdu
V2EX  ›  程序员

手撕面试题 ThreadLocal!

  •  
  •   jiangxinlingdu · 2019-06-19 12:42:08 +08:00 · 1174 次点击
    这是一个创建于 1157 天前的主题,其中的信息可能已经有所发展或是发生改变。

    说明

    面试官:讲讲你对 ThreadLocal 的一些理解。

    那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考;

    • ThreadLocal 用在什么地方?

    • ThreadLocal 一些细节!

    • ThreadLocal 的最佳实践!

    • 思考

    ThreadLocal 用在什么地方?

    讨论 ThreadLocal 用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈 ThreadLocal 的,ThreadLocal 是用在多线程的场景的!!!

    ThreadLocal 归纳下来就 2 类用途:

    • 保存线程上下文信息,在任意需要的地方可以获取!!!
    • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

    保存线程上下文信息,在任意需要的地方可以获取!!!

    由于 ThreadLocal 的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。

    常用的比如每个请求怎么把一串后续关联起来,就可以用 ThreadLocal 进行 set,在后续的任意需要记录日志的方法里面进行 get 获取到请求 id,从而把整个请求串起来。

    还有比如 Spring 的事务管理,用 ThreadLocal 存储 Connection,从而各个 DAO 可以获取同一 Connection,可以进行事务回滚,提交等操作。

    备注: ThreadLocal 的这种用处,很多时候是用在一些优秀的框架里面的,一般我们很少接触,反而下面的场景我们接触的更多一些!

    线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

    ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。但是 ThreadLocal 也有局限性,我们来看看阿里规范:

    每个线程往 ThreadLocal 中读写数据是线程隔离,互相之间不会影响的,所以ThreadLocal 无法解决共享对象的更新问题!

    由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!

    这类场景阿里规范里面也提到了:

    ThreadLocal 一些细节!

    ThreaLocal 使用示例代码:

    public class ThreadLocalTest {
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
    
            new Thread(() -> {
                try {
                    for (int i = 0; i < 100; i++) {
                        threadLocal.set(i);
                        System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    threadLocal.remove();
                }
            }, "threadLocal1").start();
    
    
            new Thread(() -> {
                try {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    threadLocal.remove();
                }
            }, "threadLocal2").start();
        }
    }
    

    代码截图:

    代码运行结果:

    从运行的结果我们可以看到 threadLocal1 进行 set 值对 threadLocal2 并没有任何影响!

    Thread、ThreadLocalMap、ThreadLocal 总览图

    Thread 类有属性变量 threadLocals (类型是 ThreadLocal.ThreadLocalMap ),也就是说每个线程有一个自己的 ThreadLocalMap,所以每个线程往这个 ThreadLocal 中读写隔离的,并且是互相不会影响的。

    一个 ThreadLocal 只能存储一个 Object 对象,如果需要存储多个 Object 对象那么就需要多个 ThreadLocal !!!

    如图:

    看到上面的几个图,大概思路应该都清晰了,我们 Entry 的 key 指向 ThreadLocal 用虚线表示弱引用 ,下面我们来看看 ThreadLocalMap:

    java 对象的引用包括 : 强引用,软引用,弱引用,虚引用 。

    因为这里涉及到弱引用,简单说明下:

    弱引用也是用来描述非必需对象的,当 JVM 进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。

    当仅仅只有 ThreadLocalMap 中的 Entry 的 key 指向 ThreadLocal 的时候,ThreadLocal 会进行回收的!!!

    ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是 Entry 是强引用,那么 Entry 里面存储的 Object,并没有办法进行回收,所以 ThreadLocalMap 做了一些额外的回收工作。

    虽然做了但是也会存在内存泄漏风险(我没有遇到过,网上很多类似场景,所以会提到后面的 ThreadLocal 最佳实践!!!

    ThreadLocal 的最佳实践!

    ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是 Entry 是强引用,那么 Entry 里面存储的 Object,并没有办法进行回收,所以 ThreadLocalMap 做了一些额外的回收工作。

    备注: 很多时候,我们都是用在线程池的场景,程序不停止,线程基本不会销毁!!!

    由于线程的生命周期很长,如果我们往 ThreadLocal 里面 set 了很大很大的 Object 对象,虽然 set、get 等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是后续在也没有操作 set、get 等方法了。

    所以最佳实践,应该在我们不使用的时候,主动调用 remove 方法进行清理。

    这里把 ThreadLocal 定义为 static 还有一个好处就是,由于 ThreadLocal 有强引用在,那么在 ThreadLocalMap 里对应的 Entry 的键会永远存在,那么执行 remove 的时候就可以正确进行定位到并且删除!!!

    最佳实践做法应该为:

    try {
        // 其它业务逻辑
    } finally {
        threadLocal 对象.remove();
    }
    

    思考

    如果面试的时候,可以把上面的内容都可以讲到,个人觉得就非常好了,回答的就挺完美了。但是如果你可以进行下面的回答,那么就更完美了。

    对于 ThreadLocal,我在看 Netty 源码的时候,还了解过 FastThreadLocal,xxxxx 一些列内容,那就是一个升级了。

    在我本地进行测试,FastThreadLocal 的吞吐量是 jdkThreadLocal 的 3 倍左右。

    备注: 由于 FastThreadLocal 内容也非常非常多,而且有很多技巧,所以准备后续专门在开一篇进行串起来!!!


    如果读完觉得有收获的话,欢迎点赞、关注、加公众号 [匠心零度] ,查阅更多精彩历史!!! image

    目前尚无回复
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2576 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 11:23 · PVG 19:23 · LAX 04:23 · JFK 07:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.