写点什么

InheritableThreadLocal 源码解析,子线程如何获取父线程的本地变量?

用户头像
徐同学呀
关注
发布于: 2021 年 04 月 13 日
InheritableThreadLocal源码解析,子线程如何获取父线程的本地变量?

一、前言

日常工作中,经常使用ThreadLocal来避免线程并发问题,每个线程访问自己的本地变量,没有竞争,没有锁,非常高效。现在有一个业务场景,需要创建一些子线程来执行任务,父线程中设置了ThreadLocal的值,想在子线程中获取,能获取到吗?答案是:不能。



了解ThreadLocal的原理,这个问题就很弱智,用脚后跟想,父线程中set,那么这个存放值的ThreadLocalMap就在父线程内,子线程的threadLocals是个null,怎么可能从子线程 get 到父线程 set 的值呢?


但是需求就要这样,该如何实现?将父线程的ThreadLocalMap复制一份给子线程?没错,java官方也是这么想的!

二、InheritableThreadLocal

1、使用方式

java 官方提供了一个类InheritableThreadLocal,使用方式上和ThreadLocal完全一样,就是类名不一样。将ThreadLocal替换为InheritableThreadLocal,就可以从子线程get到父线程set的值了。


2、继承关系

InheritableThreadLocal是如何做到的呢?源码底下见真知:


package java.lang;/** * @author  Josh Bloch and Doug Lea * @see     ThreadLocal * @since   1.2 */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); }}
复制代码


InheritableThreadLocal继承自ThreadLocal,重写了三个方法childValuegetMapcreateMap,用到Thread的一个变量inheritableThreadLocals。那就是InheritableThreadLocal初始化的ThreadLocalMap赋值给t.inheritableThreadLocalssetget也是操作t.inheritableThreadLocals


public class Thread implements Runnable {    ... ...    ThreadLocal.ThreadLocalMap threadLocals = null;    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;    ... ...}
复制代码

3、复制原理

那是如何将父线程的 map 复制给子线程的呢?


追溯到Thread初始化,会调用一个init()init初始化的东西较多,直接看重点:



真相了,创建子线程时,默认inheritThreadLocals=true,父线程即当前线程的inheritableThreadLocals!=null,则将父线程的inheritableThreadLocals复制给子线程。


// java.lang.ThreadLocal#createInheritedMapstatic ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {    return new ThreadLocalMap(parentMap);}
复制代码


//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMapprivate ThreadLocalMap(ThreadLocalMap parentMap) {    Entry[] parentTable = parentMap.table;    int len = parentTable.length;    setThreshold(len);    table = new Entry[len];    // 遍历复制    for (int j = 0; j < len; j++) {        Entry e = parentTable[j];        if (e != null) {            @SuppressWarnings("unchecked")            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();            if (key != null) {                // InheritableThreadLocal重写了childValue                Object value = key.childValue(e.value);                Entry c = new Entry(key, value);                int h = key.threadLocalHashCode & (len - 1);                while (table[h] != null)                    h = nextIndex(h, len);                table[h] = c;                size++;            }        }    }}
复制代码

三、childValue 的用意

ThreadLocalchildValue没有给出具体实现,而在InheritableThreadLocal中也只是简单实现了下。


//java.lang.ThreadLocal#childValueT childValue(T parentValue) {    throw new UnsupportedOperationException();}
复制代码


//java.lang.InheritableThreadLocal#childValueprotected T childValue(T parentValue) {    return parentValue;}
复制代码


从父线程复制ThreaLocalMap到子线程时,值从childValue函数过了一遍再赋值给Entry,是何意图?关键是InheritableThreadLocal也没做什么,但是不难猜出,ThreadLocal留了一个childValue就是让InheritableThreadLocal实现的,虽然InheritableThreadLocal没做什么,但是使用者可以继承InheritableThreadLocal重写childValue,对value做特殊处理。为什么可能要对value做特殊处理呢?


比如,设置的值是一个自定义的引用类型,那么从父线程复制到多个子线程的值就存在并发问题(值传递,地址值是共享的),所以复制的时候要保证复制给每个子线程的地址值不一样,继承InheritableThreadLocal实现childValue的深拷贝,定制化一个自己的InheritableThreadLocal


public class MyInheritableThreadLocal<T> extends InheritableThreadLocal<T>{    protected T childValue(T parentValue) {        System.out.println("MyInheritableThreadLocal。。。");        // 深拷贝        Gson gson = new Gson();        String s = gson.toJson(parentValue);        return (T)gson.fromJson(s, parentValue.getClass());    }}
复制代码


public class InheritableThreadLocalTest {    public static void main(String[] args) throws InterruptedException {        InheritableThreadLocal<Stu> itl = new MyInheritableThreadLocal<Stu>();        itl.set(new Stu());        System.out.println("父线程:" + itl.get().toString());        Thread thread1 = new Thread(new Runnable() {            @Override            public void run() {                System.out.println("子线程1:" + itl.get().toString());            }        });        thread1.start();        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                System.out.println("子线程2:" + itl.get().toString());            }        });        thread2.start();    }
static class Stu { private String name = "xxx"; }}
// 控制台打印父线程:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@49476842MyInheritableThreadLocal。。。子线程1:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@7446b2acMyInheritableThreadLocal。。。子线程2:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@75f4c190
复制代码

四、总结

  1. InheritableThreadLocal可以实现子线程获取父线程的本地变量。

  2. 子线程初始化时,若父线程(当前线程)的本地变量inheritableThreadLocals不为 null,则复制给子线程。

  3. ThreadLocal留个childValue的用意,就是让InheritableThreadLocal实现,并且可以让客户端自定义重写childValue对从父线程复制到子线程的值做特殊处理。

  4. 若父线程使用InheritableThreadLocal设置了自定义引用类型的值,复制给子线程时存在并发问题,需要自行实现childValue的深拷贝。


抛个问题:


如果使用线程池创建子线程,子线程只会初始化一次,父线程中使用InheritableThreadLocal设置值,因为复制机制是在线程初始化的时候,那么父线程只有在线程池初始化子线程时同步复制一次数据,后续父线程再修改值,就无法同步更新到线程池中的子线程了,这该怎么办呢?


只要在每次提交任务时复制就可以了,这就要对线程池以及InheritableThreadLocal做一些定制化处理,让复制机制放在每次提交任务的时候,阿里有一个开源项目给出了解决方案https://github.com/alibaba/transmittable-thread-local,后续可深入了解其实现原理。


PS: 如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!


发布于: 2021 年 04 月 13 日阅读数: 29
用户头像

徐同学呀

关注

公众号:徐同学呀 2018.09.24 加入

专注于源码分析及Java底层架构开发领域。持续改进,坦诚合作!我是徐同学,愿与你共同进步!

评论

发布
暂无评论
InheritableThreadLocal源码解析,子线程如何获取父线程的本地变量?