20

ThreadLocal的进化——InheritableThreadLocal

 4 years ago
source link: https://segmentfault.com/a/1190000021284480
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

<!-- more -->

为什么要升级

首先我们来想想,为什么要升级?这就要说起 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/

公众号:健程之道

fmQfQfR.jpg!web

buUZnqu.jpg!web


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK