【并发专题】threadLocal 连环炮

本问题对应的项目链接:高频题回答模版训练

PS:这个的话,问的也不多,但是有时候在项目中会用到这个东西,如果你在项目中写上了,那么碰到懂的面试官,还是会问

1、介绍一下 threadLocal,简单说一下他的使用场景?

2、ThreadLocal的key是哪种引用类型?为啥这么设计?

3、ThreadLocal 是怎么防止内存泄漏?

共以下 1 个回答

  • 秊 永久会员 2023年7月7日 下午5:34

    1、介绍一下 threadLocal,简单说一下他的使用场景?

    ThreadLocal 用于提供线程的局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程,是一个以 ThreadLocal 对象为键,任意对象为值的存储结构。底层是通过一个 ThreadLocalMap 来存储和管理的。

    它可以用于多线程处理多任务的场景,每个线程使用 ThreadLocal 单独保存自己的任务上下文,然后执行计算。

    2、ThreadLocal 的 key 是哪种引用类型?为啥这么设计?

    弱引用。这样设计是为了防止某些情况下 ThreadLocal 可能会导致的内存泄漏。

    3、ThreadLocal 是怎么防止内存泄漏?

    简单来说是「弱引用」加「手动调用 remove( ) 」方法。

    因为 ThreadLocalMap 主要有一个 Entry 数组,每一个 Entry 是继承了 ThreadLocal 类型的弱引用,并将其作为 key。value 是 Entry 中的一个 Object 成员变量。那么我们在创建 ThreadLocal 变量时,每一个 ThreadLocal 对象就有两个引用,一个是堆栈对它的强引用,一个是 ThreadLocalMap 中的 Entry 对它的弱引用。

    现在假设 Entry 是对 ThreadLocal 的强引用而不是弱引用,在将 ThreadLocal 置空后,堆栈上的 ThreadLocal 强引用就会消失,但 Entry 对它的强引用还在,无法被回收,并且 Entry 的 value 值也是个强引用,也无法被回收,这就导致了内存泄漏。

    将 Entry 继承自 ThreadLocal 的弱引用可以解决 ThreadLocal 的回收问题,但还是无法解决 value 的回收问题。ThreadLocal 在设计时就考虑到了这个问题,并且有一些防护措施:在调用 ThreadLocal 的 get( ),set( ) 和 remove( ) 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。所以在使用 ThreadLocal 的时候,每次用完都最好调用一下 remove( ) 方法,来防止内存泄漏。

    4、demo

    value 内存泄漏示例:

    public class Test {
    
        // 对象被回收时触发
        @Override
        protected void finalize() throws Throwable {
            System.err.println("对象被回收...");
        }
    
        public static void main(String[] args) {
            ThreadLocal local = new ThreadLocal();
            // 设置 key 为当前线程,value 为 Test 对象的键值对
            local.set(new Test());
            // 将 local 置空
            local = null;   
            // 手动触发 GC
            System.gc();
            // GC 是异步执行的,主线程 sleep 一会,等待对象回收
            // 如果 Test 被回收,会打印出回收语句
            ThreadUtil.sleep(1000);
        }
    
    
    }
    

    控制台无输出,value 内存泄漏

    正常示例演示:

    public class ThreadLocalExample {
        private static ThreadLocal threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
            // 创建并启动两个线程
            Thread thread1 = new Thread(new Worker());
            Thread thread2 = new Thread(new Worker());
            thread1.start();
            thread2.start();
        }
    
        static class Worker implements Runnable {
            @Override
            public void run() {
                // 生成一个随机数,并存储到ThreadLocal变量中
                threadLocal.set(new Random().nextInt(100));
    
                // 在同一个线程中获取并设置ThreadLocal变量的值
                System.out.println("Thread: " + Thread.currentThread().getName() +
                        ", Random Number: " + threadLocal.get());
                threadLocal.set(new Random().nextInt(100));
    
                // 等待一段时间,模拟一些其他操作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 再次在同一个线程中获取并打印ThreadLocal变量的值
                System.out.println("Thread: " + Thread.currentThread().getName() +
                        ", Random Number: " + threadLocal.get());
    
                // 清除ThreadLocal变量,以防止内存泄漏
                threadLocal.remove();
            }
        }
    }
    
    

    控制台输出:

    Thread: Thread-1, Random Number: 94
    Thread: Thread-0, Random Number: 33

    // 等待了 1s

    Thread: Thread-1, Random Number: 41
    Thread: Thread-0, Random Number: 60

    0 赞同 0 条回复

# 回答此问题

后才能回答