ThreadLocal之子父线程的恩爱情仇
900 Words|Read in about 5 Min|本文总阅读量次
我们知道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总结
- 父线程中的一个ThreadLocalMap类型的inheritableThreadLocals属性,保存一对键值。key为定义的一个InheritableThreadLocal类型的iTLocal,value为定义的Integer类型的2。
- 子线程的创建过程,会先判断是否存在父线程的inheritableThreadLocals这个属性,如果存在那么copy一份到子线程对应的inheritableThreadLocals属性中。其中JDK增加了额外的判断,是否在父线程中创建了InheritableThreadLocal类型的实例,创建了才能去copy,如果没有创建那么不做拷贝动作。
- 子线程运行中获取当前子线程中的InheritableThreadLocal类型的实例的对应的value值。直接查询当前子线程的ThreadLocalMap类型的inheritableThreadLocals属性中的键值对。其中键值对的key为iTLocal,对应的value为2。
本文源码
猜你喜欢
参考
[1] 添码星空, AS编写运行测试纯java代码,带main()函数, 2022.
[2] NCSTA, Java中的父线程与子线程, 2018.