我们知道Thread中会维护两个ThreadLocalMap,这个时候如果同时存在子父线程,子线程该如何获取父线程ThreadLocal的值

1Java的子父线程

子父线程,可以简单的人为,子线程就是一个线程A中的一个线程B,那么父线程就是线程A。

如下举一个小例子,可以得知子线程和父线程拥有不同的生命周期,可以是主线程退出,子线程依然在运行。

 1public class ThreadTest {
 2    public static void main(String[] args) throws InterruptedException {
 3        Thread parentThread = new Thread(() -> {
 4
 5            new Thread(() -> {
 6                System.out.println("son Thread exit");
 7            }, "子线程").start();
 8            System.out.println("parentThread Thread exit");
 9        }, "父线程");
10        parentThread.start();
11        System.out.println("main exit");
12    }
13}

原理说明

如果main方法中没有创建其他线程,那么当main方法返回时JVM就会结束Java应用程序。但如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法返回(主线程结束)JVM也不会结束,要一直等到该程序所有线程全部结束才结束Java程序(另外一种情况是:程序中调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。这时JVM也会结束该程序)。

2子线程获取父类的ThreadLocal

2.1demo打印

如果想在as中运行纯java文件,可以点击这里

如果使用Lamda表达式,推荐使用JavaVersion.VERSION_1_8。

 1public class ThreadTest {
 2    public static void main(String[] args) throws InterruptedException {
 3        Thread parentThread = new Thread(() -> {
 4            ThreadLocal<Integer> tLocal = new ThreadLocal<>();
 5            tLocal.set(1);
 6            InheritableThreadLocal<Integer> iTLocal = new InheritableThreadLocal<>();
 7            iTLocal.set(2);
 8            System.out.println("->>>threadLocal=" + tLocal.get());
 9            System.out.println("->>>inheritableThreadLocal=" + iTLocal.get());
10
11            new Thread(() -> {
12                System.out.println("threadLocal=" + tLocal.get());
13                System.out.println("inheritableThreadLocal=" + iTLocal.get());
14            }, "子线程").start();
15        }, "父线程");
16        parentThread.start();
17    }
18}

结果

3源码分析

这里笔者看的是AndroidSDK中的Api30,和实际的JDK1.8会有区别,下面会有说明。

查看Thread源码,里面定义了两个ThreadLocalMap。关于Thread,ThreadLocal和ThreadLocalMap关系,可以点击这里

1//Thread.java
2ThreadLocal.ThreadLocalMap threadLocals = null;
3ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

3.1父线程

首先,还是看父线程。父线程只有两个操作

3.1.1.创建两个ThreadLocal

创建两个ThreadLocal,分别是tLocal和iTLocal

1ThreadLocal<Integer> tLocal = new ThreadLocal<>();
2InheritableThreadLocal<Integer> iTLocal = new InheritableThreadLocal<>();

3.1.2ThreadLocal的set方法

对这两个ThreadLocal做了一个set操作

1tLocal.set(1);
2iTLocal.set(2);

查看tLocal的set方法,可以知道ThreadLocal会和Thread绑定在一起

 1//ThreadLocal.java#set
 2public void set(T value) {
 3    //获取当前的线程
 4    Thread t = Thread.currentThread();
 5    //查找当前线程的map表,这个表中会存放键值对,key是ThreadLocal,value为ThreadLocal需要存入的值
 6    ThreadLocal.ThreadLocalMap map = this.getMap(t);
 7    if (map != null) {
 8        map.set(this, value);
 9    } else {
10        //因为map会不存在,这里会走到,去创建表
11        this.createMap(t, value);
12    }
13}
14
15void createMap(Thread t, T firstValue) {
16    t.threadLocals = new ThreadLocalMap(this, firstValue);
17}

这里的创建其实就是创建一个16个长度的Entry组成的map,因为这里有魔数的存在,所以是从2开始排列。

1//Thread.java#ThreadLocalMap
2ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
3    table = new Entry[INITIAL_CAPACITY];
4    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
5    table[i] = new Entry(firstKey, firstValue);
6    size = 1;
7    setThreshold(INITIAL_CAPACITY);
8}

同理,iTLocal的set方法也是类似。区别在于有两个函数是复写了。

1//InheritableThreadLocal.java#getMap和createMap
2ThreadLocalMap getMap(Thread t) {
3    return t.inheritableThreadLocals;
4}
5
6void createMap(Thread t, T firstValue) {
7    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
8}

创建完成之后如下图所示。

3.2子线程

3.2.1子线程Thread构造

 1//Thread.java#Thread
 2public Thread(Runnable target) {
 3    init(null, target, "Thread-" + nextThreadNum(), 0);
 4}
 5
 6private void init(ThreadGroup g, Runnable target, String name,
 7                  long stackSize) {
 8    init(g, target, name, stackSize, null);
 9}
10
11private void init(ThreadGroup g, Runnable target, String name,
12                  long stackSize, AccessControlContext acc) {
13    if (name == null) {
14        throw new NullPointerException("name cannot be null");
15    }
16
17    this.name = name;
18
19    Thread parent = currentThread();
20    if (g == null) {
21        g = parent.getThreadGroup();
22
23    }
24
25    g.addUnstarted();
26
27    this.group = g;
28    this.daemon = parent.isDaemon();
29    this.priority = parent.getPriority();
30
31    this.target = target;
32    //最终会走到这里,其中parent的类型是Thread,那么这里的parent就是子线程还在创建过程中的父线程
33    init2(parent);
34
35    this.stackSize = stackSize;
36
37    tid = nextThreadID();
38}

子线程Thread初始化,有一个关于和父线程的判断。

如果父线程中存在一个inheritableThreadLocals属性且不为null,那么子线程中的inheritableThreadLocals创建起来。

1//Thread.java#init2
2private void init2(Thread parent) {
3    this.contextClassLoader = parent.getContextClassLoader();
4    this.inheritedAccessControlContext = AccessController.getContext();
5    if (parent.inheritableThreadLocals != null) {
6        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
7            parent.inheritableThreadLocals);
8    }
9}

因为笔者用得是AndroidSDK的Api30,实际上这里的构造和JDK1.8有一定差距,主要看判断处

 1public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
 2    this(group, target, name, stackSize, (AccessControlContext)null, true);
 3}
 4
 5//这个Thread的构造,可以直接定义inheritThreadLocals的值,是否进行后续的copy操作
 6public Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) {
 7    this(group, target, name, stackSize, (AccessControlContext)null, inheritThreadLocals);
 8}
 9
10private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
11    this.daemon = false;
12    this.stillborn = false;
13    this.threadLocals = null;
14    this.inheritableThreadLocals = null;
15    this.blockerLock = new Object();
16    if (name == null) {
17        throw new NullPointerException("name cannot be null");
18    } else {
19        this.name = name;
20        Thread parent = currentThread();
21        ...
22        this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
23        this.target = target;
24        this.setPriority(this.priority);
25        //这里的判断增加了inheritThreadLocals,这个inheritThreadLocals值是从上面传进来的true。
26        //JDK源码设置的好处就是,可以从外部自定义,我可以传入false,不进行下面的操作,扩展性更强
27        if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
28            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
29        }
30
31        this.stackSize = stackSize;
32        this.tid = nextThreadID();
33    }
34}

JDK源码设置的好处就是,可以从外部Thread构造的时候决定是否需要创建inheritableThreadLocals。

开始创建Map

1//ThreadLocal.java#createInheritedMap
2static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
3    return new ThreadLocalMap(parentMap);
4}

传入Map的拷贝构造

 1//ThreadLocal.java#ThreadLocalMap
 2private ThreadLocalMap(ThreadLocalMap parentMap) {
 3    Entry[] parentTable = parentMap.table;
 4    int len = parentTable.length;
 5    setThreshold(len);
 6    table = new Entry[len];
 7
 8    for (int j = 0; j < len; j++) {
 9        Entry e = parentTable[j];
10        if (e != null) {
11            @SuppressWarnings("unchecked")
12            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
13            if (key != null) {
14                Object value = key.childValue(e.value);
15                Entry c = new Entry(key, value);
16                int h = key.threadLocalHashCode & (len - 1);
17                while (table[h] != null)
18                    h = nextIndex(h, len);
19                table[h] = c;
20                size++;
21            }
22        }
23    }
24}

构造完成之后如下图所示

3.2.2子线程中的ThreadLocal获取

首先是tLocal的get方法

 1//ThreadLocal.java#get
 2public T get() {
 3    Thread t = Thread.currentThread();
 4    ThreadLocalMap map = getMap(t);
 5    if (map != null) {
 6        ThreadLocalMap.Entry e = map.getEntry(this);
 7        if (e != null) {
 8            @SuppressWarnings("unchecked")
 9            T result = (T)e.value;
10            return result;
11        }
12    }
13    //因为没有Map,所以会走到这里
14    return setInitialValue();
15}

因为没有默认的threadLocals表,那么就自己创建一个表,返回的是null

 1//ThreadLocal.java#setInitialValue
 2private T setInitialValue() {
 3    //初始化一个value值,默认是null
 4    T value = initialValue();
 5    Thread t = Thread.currentThread();
 6    ThreadLocalMap map = getMap(t);
 7    if (map != null)
 8        map.set(this, value);
 9    else
10        //因为createMap在上面复述过,这里不再展开,意思就是创建一个键值对,其中key是tLocal,value为null
11        createMap(t, value);
12    return value;
13}

其次是iTLocal的get方法,跟上面类似。唯一的区别是子线程中是存在inheritableThreadLocals表的。

 1//ThreadLocal.java#get
 2public T get() {
 3    Thread t = Thread.currentThread();
 4    ThreadLocalMap map = getMap(t);
 5    if (map != null) {
 6        ThreadLocalMap.Entry e = map.getEntry(this);
 7        if (e != null) {
 8            @SuppressWarnings("unchecked")
 9            //因为存在Map,且里面的value的值也是不为null的,那么可以返回这个value值
10            T result = (T)e.value;
11            return result;
12        }
13    }
14    return setInitialValue();
15}

最终如下图所示

4总结

  1. 父线程中的一个ThreadLocalMap类型的inheritableThreadLocals属性,保存一对键值。key为定义的一个InheritableThreadLocal类型的iTLocal,value为定义的Integer类型的2。
  2. 子线程的创建过程,会先判断是否存在父线程的inheritableThreadLocals这个属性,如果存在那么copy一份到子线程对应的inheritableThreadLocals属性中。其中JDK增加了额外的判断,是否在父线程中创建了InheritableThreadLocal类型的实例,创建了才能去copy,如果没有创建那么不做拷贝动作。
  3. 子线程运行中获取当前子线程中的InheritableThreadLocal类型的实例的对应的value值。直接查询当前子线程的ThreadLocalMap类型的inheritableThreadLocals属性中的键值对。其中键值对的key为iTLocal,对应的value为2。

本文源码

这点这里查看

猜你喜欢

ThreadLocal之初出茅庐

参考

[1] 添码星空, AS编写运行测试纯java代码,带main()函数, 2022.

[2] NCSTA, Java中的父线程与子线程, 2018.

[3] 只会一点java, ThreadLocal终极源码剖析-一篇足矣!, 2017.

[4] 刘望舒, 京东一面:子线程如何获取父线程ThreadLocal的值, 2022.