前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >InheritableThreadLocal源码解析,子线程如何获取父线程的本地变量?

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

作者头像
100000860378
发布2021-04-16 12:05:46
1.6K0
发布2021-04-16 12:05:46
举报

一、前言

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

ThreadLocalTest

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

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

二、InheritableThreadLocal

1、使用方式

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

InheritableThreadLocalTest

2、继承关系

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

代码语言:javascript
复制
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

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

3、复制原理

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

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

inheritableThreadLocals复制机制

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

代码语言:javascript
复制
// java.lang.ThreadLocal#createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
代码语言:javascript
复制
//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap
private 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中也只是简单实现了下。

代码语言:javascript
复制
//java.lang.ThreadLocal#childValue
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}
代码语言:javascript
复制
//java.lang.InheritableThreadLocal#childValue
protected T childValue(T parentValue) {
    return parentValue;
}

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

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

代码语言:javascript
复制
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());
    }
}
代码语言:javascript
复制
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@49476842
MyInheritableThreadLocal。。。
子线程1:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@7446b2ac
MyInheritableThreadLocal。。。
子线程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: 如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、InheritableThreadLocal
    • 1、使用方式
      • 2、继承关系
        • 3、复制原理
        • 三、childValue的用意
        • 四、总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档