美文网首页
InheritableThreadLocal源码解析,子线程如何

InheritableThreadLocal源码解析,子线程如何

作者: 徐同学呀 | 来源:发表于2021-04-14 12:55 被阅读0次

一、前言

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

ThreadLocalTest

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

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

二、InheritableThreadLocal

1、使用方式

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

InheritableThreadLocalTest

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初始化的东西较多,直接看重点:

inheritableThreadLocals复制机制

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

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

//java.lang.ThreadLocal#childValue
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}
//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

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

相关文章

网友评论

      本文标题:InheritableThreadLocal源码解析,子线程如何

      本文链接:https://www.haomeiwen.com/subject/fjvklltx.html