我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
一、前言
1.1 基础
1.2 InheritableThreadLocal 能干嘛?
- 能让子线程读取到父线程的变量。(可以用来监控 traceId 方法链)
二、架构
2.1 代码架构
2.2 正常初始化子进程
- 此图片有助于理解引用传递和值传递
三、实战 demo
- 看上图可以理解下面所有demo
3.1 ThreadLocal 无法传递线程内变量
public class _22_01_TestInheritableThreadLocal {
// 1.通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new ThreadLocal();
// InheritableThreadLocal.withInitial(() -> 100); // 假设初始账户有100块钱
public static void main(String[] args) throws Exception {
balance.set(20);
new Thread(new TaskDemo(), "A").start();
// 让子线程先走
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
}
}
- 输出:
A --> balance[null]
main --> balance[20]
- 结论:ThreadLocal 无法传递线程内变量
3.2 实现数值的复制传递
- 代码:
public class _22_02_TestInheritableThreadLocal {
// 1.通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
// InheritableThreadLocal.withInitial(() -> 100); // 假设初始账户有100块钱
public static void main(String[] args) throws Exception {
balance.set(20);
new Thread(new TaskDemo(), "A").start();
// 让子线程先走
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
}
}
- 输出:
A --> balance[20]
main --> balance[20]
- 结论:父线程定义的线程内局部变量已经被子线程取到了。
3.3 变量在父线程的修改,不影响子线程(数值传递)
- 代码:
public class _22_03_TestInheritableThreadLocal {
// 加个lock 控制流程顺序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 设置 balance
balance.set(20);
new TaskDemo().start();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
lock.lock();
// 2. 修改 balance 的值,看下子线程的效果
balance.set(40);
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo extends Thread {
public void run() {
// 看下父线程的变量能否继承袭来
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
try {
lock.lock();
condition.await(); // 注意 await 本身会释放 lock
// 看下父线程的修改对子类的影响
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
- 输出:
Thread-0 --> balance[20]
main --> balance[20]
main set New balance ===
Thread-0 --> balance[20]
main --> balance[40]
- 结论:
- 看输出父线程修改了变量的值之后,子线程仍然输出了老值。
- 若一开始父线程没有初始化,那么后续的 set 对子线程没影响。详看情景 3.9
3.4 变量在子线程的修改,不影响父线程(数值传递)
- 这个情景跟上面 3.3 一样,不做冗余介绍。
3.5 变量在父线程的修改,不影响子线程(引用传递重新New)
- 代码
public class _22_04_TestInheritableThreadLocal {
// 加个lock 控制流程顺序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Wallet> walletThreadLocal = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 设置 balance
walletThreadLocal.set(new Wallet(20));
// walletThreadLocal.get().setBalance(40);
new Thread(new TaskDemo(),"A").start();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
lock.lock();
// 2. 修改 balance 的值,看下子线程的效果
walletThreadLocal.set(new Wallet(40));
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
}
private static class TaskDemo implements Runnable {
public void run() {
// 看下父线程的变量能否继承袭来
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
try {
lock.lock();
condition.await(); // 注意 await 本身会释放 lock
// 看下父线程的修改对子类的影响
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
@Data
@AllArgsConstructor
private static class Wallet{
private int balance;
@Override
public String toString(){
return "Wallet.balance = [" + balance + "]";
}
}
}
- 输出:
AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [20]
mainWallet.balance = [40]
- 结论:
- 父类指针指向的对象变了。子类的依旧没变。所以改了跟他没关系。
3.6 变量在子线程的修改,不影响父线程(引用传递重新New)
- 同上 3.4
3.7 变量在父线程的修改,影响了子线程(改了指针指向的对象内的内容)
- 把 情景 3.5 的代码改一段,你可以看到效果
walletThreadLocal.set(new Wallet(40));
改成
walletThreadLocal.get().setBalance(40);
输出:
AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [40]
mainWallet.balance = [40]
- 结论:
- 数值已经被改了。
- 因为父线程和子线程指向的是同一个对象。你把对象里面的东西给改了。
3.8 变量在子线程的修改,影响了父线程(改了指针指向的对象内的内容)
- 同上 3.7
3.9 变量若一开始父线程没有初始化,那么后续的 set 对子线程没作用。
- 代码
// 3.9 变量若一开始父线程没有初始化,那么后续的 set 对子线程没影响。
public class _22_06_TestInheritableThreadLocal {
// 加个lock 控制流程顺序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 设置 balance
new TaskDemo().start();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
lock.lock();
// 2. 修改 balance 的值,看下子线程的效果
balance.set(40);
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 让子线程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo extends Thread {
public void run() {
// 看下父线程的变量能否继承袭来
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
try {
lock.lock();
condition.await(); // 注意 await 本身会释放 lock
// 看下父线程的修改对子类的影响
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
- 输出:
Thread-0 --> balance[null]
main --> balance[null]
main set New balance ===
Thread-0 --> balance[null]
main --> balance[40]
- 结论:
- 变量若一开始父线程没有初始化,那么后续的 set 对子线程没影响。
- 这个实验的目的和情景三 验证的结论一致。
四、InheritableThreadLocal 源码剖析
4.1 类 InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
protected T childValue(T parentValue) {
return parentValue;
}
}
从 类UML图 可以得知:
- 重写类父类 ThreadLocal 的两个方法 getMap + createMap
- 实现了父类ThreadLocal的 childValue方法(父类的实现是空的)
4.2 getMap 方法 (getter)
- java.lang.InheritableThreadLocal#getMap 方法
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
4.2.1 与重载之前的区别
- 重载之前的 ThreadLocal.getMap 方法
- 区别:ThreadLocal返回的是 t.threadLocals,重载后 InheritableThreadLocal 返回的是 t.inheritableThreadLocals。
4.2.2 查看上游
- MAC+IDEA 直接Option + F7 查看方法在哪里使用。
- 总共有4个地方使用到了这个方法:
- java.lang.ThreadLocal#get 方法
- java.lang.ThreadLocal#setInitialValue 方法
- java.lang.ThreadLocal#set 方法
- java.lang.ThreadLocal#remove 方法
4.3 createMap 方法 (Setter)
- java.lang.InheritableThreadLocal#createMap 方法
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
4.3.1 ThreadLocalMap 构造方法
- java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap) 方法
/**
* 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
* 该函数只被 createInheritedMap() 调用.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 浅复制,让子线程指向了原本父类内部的指向的 Entry表
// 临时表
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
table = new Entry[len];
逐一复制 parentMap 的记录
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
// e != null,说明不是脏数据。
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 调用子线程复写的 childValue 方法,处理 value,这个地方仅仅是浅复制
Object value = key.childValue(e.value);
// 生成新的 Entry
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
4.3.1.1 Object value = key.childValue(e.value);
- 绝不是网上有些人说的,只是为了减轻阅读代码的难度。而是留给后续子类你可以灵活处理,比如 InheritableThreadLocal 只是简单的浅复制,你完全可以继续覆写该方法改成深负责,或者你对数值进行处理,比如+1或者-1处理。或者可以把 traceId 加入前缀或者后缀
4.3.2 与重载之前的区别
- 重载之前的 ThreadLocal#createMap 方法
- 区别:ThreadLocal#createMap 是初始化 t.threadLocals ,重载后 InheritableThreadLocal#createMap 是初始化 t.inheritableThreadLocals 。
4.3.3 查看上游
- MAC+IDEA 直接Option + F7 查看方法在哪里使用。
- 总共有2个地方使用到了这个方法:
4.4 getMap 与 createMap 总结
- 一个是 getter ,一个是 setter
- override 之前操作的是 t.threadLocals ,之后是操作 t.inheritableThreadLocals (Thread中一个必定为null)
- 看下 Thread.class 使你眼前一亮
4.5 Thread 类
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
4.6 childValue 方法
-
MAC+IDEA 直接Option + F7 查看方法在哪里使用。
-
继续MAC+IDEA 直接Option + F7 查看方法在哪里使用。
4.6.2 ThreadLocal#createInheritedMap 方法
- java.lang.ThreadLocal#createInheritedMap 方法
- 创建 InheritableThreadLocal 的工厂方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
- 继续MAC+IDEA 直接Option + F7 查看方法在哪里使用。
4.6.3 Thread#init 方法
- java.lang.Thread#init 方法(注意:这个地方跑到了 Thread类来了)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
4.6.3.1 注意:parent.inheritableThreadLocals != null
注意: parent.inheritableThreadLocals != null ,这个前提是 父线程已经 set 了,也就是场景9强调的情况:变量若一开始父线程没有初始化,那么后续的 set 对子线程没作用。
- 继续MAC+IDEA 直接Option + F7 查看方法在哪里使用。
- 不断查上去,是第一个
- java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
再上去,我们看到了是 Thread类的初始化。
4.6.4 Thread 初始化
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
- 查看 Thread.class
五、番外篇
下一章节:【线程】ThreadGroup 实战与剖析 (十七)
上一章节:【线程】ThreadLocal 内存泄漏问题(十五)