首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal的进化——InheritableThreadLocal

ThreadLocal的进化——InheritableThreadLocal

作者头像
健程之道
发布2019-12-18 11:23:08
2900
发布2019-12-18 11:23:08
举报
文章被收录于专栏:健程之道健程之道

之前有介绍过 ThreadLocal,JDK 后来针对此做了一个升级版本 InheritableThreadLocal,今天就来好好介绍下。

为什么要升级

首先我们来想想,为什么要升级?这就要说起 ThreadLocal 的功能了。

我们知道,ThreadLocal 设计初衷是为了在多线程环境下,针对每一个线程能有一个自己的副本,这样可以在一定程度上解决多线程并发修改的问题。但是,我们可以在此基础上做一个拓展,比如context,我们可以利用 ThreadLocal 针对每一个线程都有一个自己的上下文,一般都是写成ThreadLocal<Context>,这样在这个线程上做的所有修改都可以被大家利用到。

此时设想一下,假如我们新建一个子线程,那这个子线程可以获取到父线程的context吗?理论上希望可以达成这样的效果,实际上呢?让我们看看:

public class ThreadLocalContext {

    private static ThreadLocal<Context> context = new ThreadLocal<>();

    static class Context {

        String name;

        int value;
    }

    public static void main(String[] args) {
        Context context = new Context();
        context.name = "mainName";
        context.value = 10;
        ThreadLocalContext.context.set(context);

        Thread childThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Context childContext = ThreadLocalContext.context.get();
                        System.out.println(childContext.name);
                        System.out.println(childContext.value);
                    }
                }
        );
        childThread.start();
    }
}

运行 main 方法之后,直接在子线程中抛错,这样确实符合我们的预期,但如果我们想达到子线程可以获取到父线程的 context这样的效果该如何做呢?

首先想到的就是在生成子线程的时候,将父线程 ThreadLocal 里的值传给子线程。这样做虽然能达到效果,但过程比较繁杂,且代码侵入性强。

这个时候就可以用InheritableThreadLocal了。

什么是 InheritableThreadLocal

看源码

先让我们看看它的源码,大家不要怕,它的源码很少:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

        ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

首先它继承自 ThreadLocal,那么它其实就是 ThreadLocal 的一个拓展版本,接下来就是这三个方法,其实这三个方法在 ThreadLocal 都是有的,我们来看看:

    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

除了childValue方法在 ThreadLocal 中是抛出异常的,其余两个方法在两个类中都几乎是一样,只是针对的对象不同而已,但threadLocalsinheritableThreadLocals都是ThreadLocal.ThreadLocalMap类型,这个在之前的文章中有说过,就是一个 key 为弱引用的 Entry,这个倒不是重点。

我们再来看看 inheritableThreadLocals 是在何时被初始化的,从源码可以得知:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
                // 省略无关代码
                ...
                Thread parent = currentThread();
                ...
                // 省略无关代码
                ...
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
                ...
        }

当我们通过父线程调用 Thread 的构造方法生成一个子线程时,其构造方法最终会调用这个 init 方法。从这儿可以看出, inheritableThreadLocals 是来自于父线程的 inheritableThreadLocals,那这样也就解释了为什么 inheritableThreadLocals 支持在子线程中使用父线程中存储的变量。

如何使用

让我们还是回到上文提到的 context 的例子,用 InheritableThreadLocal 进行改造:

public class ThreadLocalContext {

    private static InheritableThreadLocal<Context> context = new InheritableThreadLocal<>();

    static class Context {

        String name;

        int value;
    }

    public static void main(String[] args) {
        Context context = new Context();
        context.name = "mainName";
        context.value = 10;
        ThreadLocalContext.context.set(context);

        Thread childThread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Context childContext = ThreadLocalContext.context.get();
                        System.out.println(childContext.name);
                        System.out.println(childContext.value);
                    }
                }
        );
        childThread.start();
    }
}

运行后,不仅没有抛出异常,而且在子线程中输出了父线程设置好的值。皆大欢喜!

总结

今天分享了 InheritableThreadLocal,主要是因为周三在携程的分享会上听到了别人谈了这方面的分享,主讲人讲了一个更加普遍的问题,如果我们用线程池提交任务的话,线程池中的线程在执行任务时,如何能够获得提交任务的线程的 context,这时就要用到阿里的开源组件 TTL,我会在之后进行介绍。

加入携程也有1个月了,虽然感受到大公司有不少的弊端,比如沟通难等,但也有不少的优点,比如技术分享会,虽然也是忙里偷闲去参加的,但有了更多和技术相关的可以学习和交流的机会,也挺好的。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 健程之道 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要升级
  • 什么是 InheritableThreadLocal
    • 看源码
      • 如何使用
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档