时隔半年,ThreadLocal让我再次碰上(每天一题,一起进大厂)

279 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

每天一题,一起进大厂,欢迎大家点击阅读。《一起进大厂系列》是我最近刚开始准备的,主要是针对大厂面试题进入针对性深入学习。欢迎大家关注该专栏,会进行不间断更新。一起加油,一起进大厂。话不多说,进入今天的正题。

去年我专门针对ThreadLocal讲过几个使用场景案例,不了解的可以去看下。由于我开始攻克面试题,所以我们今天来一起针对性的学习下

干货!ThreadLocal 使用场景

干货!ThreadLocal 使用场景02

image.png

温故知新

先来简单回顾下ThreadLoca:

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。

那么就引起今天的面试题了,既然是多线程,为每个线程创建一个副本。请问子线程如何获取父线程的值?。

还记得我去年写的文章最后写到:

一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法

是的,今天我们对ThreadLocalMap好好研究下:

摸着测试例子过河

public static void main(String[] args) throws InterruptedException {

    //父线程
    Thread parentParent = new Thread(() -> {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        threadLocal.set(1);
        
        //这个是关键
        InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();
        inheritableThreadLocal.set(2);

        //模拟子线程去获取父线程的值
        new Thread(() -> {
            //同样都是子线程获取父线程,两者究竟能获取到什么,可以猜下
            System.out.println("threadLocal=" + threadLocal.get());
            System.out.println("inheritableThreadLocal=" + inheritableThreadLocal.get());
        }).start();
        
        
    }, "父线程");
    
    //启动父线程
    parentParent.start();
}

我们运行结果下:

threadLocal=null
inheritableThreadLocal=2

不言而喻,inheritableThreadLocal拿到了父线程的值。

其实原理很简单 我们接着往下看。

看源码

先看Thread: 内部维护了两个ThreadLocalMap

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

接着往下看,其构造方法调用了init方法, init方法把inheritThreadLocals值设置为了true

public Thread() {
    //调用init
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

这里之所以放成图片 是为了让大家好看到这个提示inheritThreadLocals 为true

image.png

接着跟下去。

image.png

可以看到 当inheritThreadLocals的值为true并且其父线程的inheritableThreadLocals不为null时, 把其父线程inheritableThreadLocals 赋值给当前线程的inheritableThreadLocals

这就是子线程可以获取到父线程ThreadLocal值的关键。

发现关键信息,回头再看源码

我们回过头来再看InheritableThreadLocal的 get方法,其实就是ThreadLocal的get()方法。

public T get() {
    Thread t = Thread.currentThread();
    //重写
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

这里注意:InheritableThreadLocal 对ThreadLocal 的getMap()方法进行重写

image.png

我们对上述图中方法做下解释


ThreadLocalMap getMap(Thread t) {
 //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
    return t.threadLocals; 
}

void createMap(Thread t, T firstValue) {
    //创建给ThreadLocalMap的table属性赋值,并且将firstValue放在数组首位。
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

看到这里明白了吧!!!

总结

  • InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap方法。

  • createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

  • 其中createMap方法在被调用的时候,创建的是inheritableThreadLocal而不是threadLocals。

  • 那么同理,getMap方法在当前调用者线程调用get方法的时候返回的也不是threadLocals而是inheritableThreadLocal。

OK,今天我们就学习到这里,一起加油,一起坚持,一起进大厂!!!

啰嗦一句,欢迎大家学习一起进大厂系列

看完这篇文章才知道什么是java反射(每天一题,一起进大厂)

最全的redis数据结构,建议收藏(每天一题,一起进大厂)