【线程】InheritableThreadLocal 剖析 (十六)

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码


一、前言

1.1 基础

  1. 必须先看下【线程】ThreadLocal 剖析 (十四)
  2. 有时间再看下 【线程】ThreadLocal 内存泄漏问题(十五)

=== 点击查看top目录 ===

1.2 InheritableThreadLocal 能干嘛?

  1. 能让子线程读取到父线程的变量。(可以用来监控 trace 方法链)

=== 点击查看top目录 ===

二、架构

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]
  • 结论:父线程定义的线程内局部变量已经被子线程取到了。

=== 点击查看top目录 ===

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]

  • 结论:
  1. 看输出父线程修改了变量的值之后,子线程仍然输出了老值。
  2. 若一开始父线程没有初始化,那么后续的 set 对子线程没影响。详看情景 3.9

3.4 变量在子线程的修改,不影响父线程(数值传递)

  • 这个情景跟上面 3.3 一样,不做冗余介绍。

=== 点击查看top目录 ===

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]
  • 结论:
  1. 父类指针指向的对象变了。子类的依旧没变。所以改了跟他没关系。

=== 点击查看top目录 ===

3.6 变量在子线程的修改,不影响父线程(引用传递重新New)

  • 同上 3.4

=== 点击查看top目录 ===

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]
  • 结论:
  1. 数值已经被改了。
  2. 因为父线程和子线程指向的是同一个对象。你把对象里面的东西给改了。

=== 点击查看top目录 ===

3.8 变量在子线程的修改,影响了父线程(改了指针指向的对象内的内容)

  • 同上 3.7

=== 点击查看top目录 ===

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]
  • 结论:
  1. 变量若一开始父线程没有初始化,那么后续的 set 对子线程没影响。
  2. 这个实验的目的和情景三 验证的结论一致。

=== 点击查看top目录 ===

四、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图 可以得知:

  1. 重写类父类 ThreadLocal 的两个方法 getMap + createMap
  2. 实现了父类ThreadLocal的 childValue方法(父类的实现是空的)

=== 点击查看top目录 ===

4.2 getMap 方法 (getter)

  • java.lang.InheritableThreadLocal#getMap 方法
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
4.2.1 与重载之前的区别
  1. 重载之前的 ThreadLocal.getMap 方法
  2. 区别:ThreadLocal返回的是 t.threadLocals,重载后 InheritableThreadLocal 返回的是 t.inheritableThreadLocals。
4.2.2 查看上游
  • MAC+IDEA 直接Option + F7 查看方法在哪里使用。
    -
  • 总共有4个地方使用到了这个方法:
  1. java.lang.ThreadLocal#get 方法
  2. java.lang.ThreadLocal#setInitialValue 方法
  3. java.lang.ThreadLocal#set 方法
  4. java.lang.ThreadLocal#remove 方法

=== 点击查看top目录 ===

4.3 createMap 方法 (Setter)

  • java.lang.InheritableThreadLocal#createMap 方法
void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

=== 点击查看top目录 ===

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++;
            }
        }
    }
}

=== 点击查看top目录 ===

4.3.1.1 Object value = key.childValue(e.value);
  1. 绝不是网上有些人说的,只是为了减轻阅读代码的难度。而是留给后续子类你可以灵活处理,比如 InheritableThreadLocal 只是简单的浅复制,你完全可以继续覆写该方法改成深负责,或者你对数值进行处理,比如+1或者-1处理。或者可以把 traceId 加入前缀或者后缀

=== 点击查看top目录 ===

4.3.2 与重载之前的区别
  1. 重载之前的 ThreadLocal#createMap 方法
  2. 区别:ThreadLocal#createMap 是初始化 t.threadLocals ,重载后 InheritableThreadLocal#createMap 是初始化 t.inheritableThreadLocals 。

=== 点击查看top目录 ===

4.3.3 查看上游
  • MAC+IDEA 直接Option + F7 查看方法在哪里使用。
    在这里插入图片描述
  • 总共有2个地方使用到了这个方法:
  1. java.lang.ThreadLocal#setInitialValue 方法
  2. java.lang.ThreadLocal#set 方法

=== 点击查看top目录 ===

4.4 getMap 与 createMap 总结

  1. 一个是 getter ,一个是 setter
  2. override 之前操作的是 t.threadLocals ,之后是操作 t.inheritableThreadLocals (Thread中一个必定为null)
  3. 看下 Thread.class 使你眼前一亮

=== 点击查看top目录 ===

4.5 Thread 类

public class Thread implements Runnable {
	...
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}

=== 点击查看top目录 ===

4.6 childValue 方法

4.6.2 ThreadLocal#createInheritedMap 方法
  • java.lang.ThreadLocal#createInheritedMap 方法
  • 创建 InheritableThreadLocal 的工厂方法
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
  • 继续MAC+IDEA 直接Option + F7 查看方法在哪里使用。
    在这里插入图片描述

=== 点击查看top目录 ===

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);
...

=== 点击查看top目录 ===

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类的初始化。

=== 点击查看top目录 ===

4.6.4 Thread 初始化
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

=== 点击查看top目录 ===

五、番外篇

上一章节:【线程】ThreadGroup 实战与剖析 (十七)
上一章节:【线程】ThreadLocal 内存泄漏问题(十五)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章