ThreadLocal 系列之 InheritableThreadLocal

相关文章:

ThreadLocal 存在的问题

上一篇文章中简单介绍了什么是 ThreadLocal,以及为什么我们可以根据它去操作线程级别的变量。但是 ThreadLocal 严格绑定当前线程,不适用于线程切换的情况,比如在分布式 Tracing 中,你无法控制会不会有服务存在线程切换。先看一个例子:

public class InheritableThreadLocalTest1 {

    private static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();

    public static void main(String[] args) {
        HOLDER.set(1);
        System.out.println(currentName()+":"+HOLDER.get());
        new Thread(()-> System.out.println(currentName()+":"+HOLDER.get())).start();
    }

    private static String currentName(){
        return Thread.currentThread().getName();
    }
}

输出结果:

main:1
Thread-0:null

可以发现,在子线程(即 Thread-0 线程)中无法获取父线程(即 main 线程)的数据,用官方的翻译来说的话就是 ThreadLocal 不支持继承。这个也很好理解,虽然都是 Thread 类的实例,但是不同实例之间怎么可能直接获取对方成员变量的数据呢。

一种解决思路

一般我们多线程操作都是会比如实现 Runable,封装一个 Task 类,我们可以提前在 Task 类中传入父线程的变量,示例代码如下:

/**
 * @author 
 * @Description 执行任务包装类
 * @Date 创建于 2020-03-29 14:51
 */
public class MyTask implements Runnable {

    private Runnable runnable;

    private Integer inheritableValue;

    @Override
    public void run() {
        try {
            InheritableThreadLocalTest.HOLDER.set(inheritableValue);
            runnable.run();
        }finally {
            InheritableThreadLocalTest.HOLDER.remove();
        }
    }

    public MyTask(Runnable runnable) {
        this.runnable = runnable;
        this.inheritableValue =  InheritableThreadLocalTest.HOLDER.get();
    }
}

测试代码:

public class InheritableThreadLocalTest {

    public static final ThreadLocal<Integer> HOLDER = new ThreadLocal<>();

    public static void main(String[] args) {
        HOLDER.set(1);
        System.out.println(currentName() + ":" + HOLDER.get());
        Runnable task = ()-> System.out.println(currentName()+":"+HOLDER.get());
        new Thread(task,"simple task").start();

        new Thread(new MyTask(task),"inheritable task").start();
    }

    private static String currentName() {
        return Thread.currentThread().getName();
    }
}

输出结果:

main:1
simple task:null
inheritable task:1

会发现是可以获取到父线程的值。JDK 提供了 InheritableThreadLocal 解决 ThreadLocal 继承的问题。

InheritableThreadLocal

InheritableThreadLocal 继承了 ThreadLocal,有这么几个方法:
在这里插入图片描述

结合上一篇文章中分析的 ThreadLocal#set 方法,优先看下 createMap 方法:

void createMap(Thread t, T firstValue) {
	t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

可以发现与 ThreadLocal 不同的是,InheritableThreadLocal 关注的是 ThreadinheritableThreadLocals 变量:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

也是 ThreadLocalMap 类型,咋一看并没有什么特殊的,参考上文我们自己实现的一种解决思路,我们是在子线程执行方法前将父线程的值设置进去的,看看 JDK 是怎么做的,先看 Thread#init 方法:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        this.name = name;
				//获取当前线程
        Thread parent = currentThread();
        ...
  			//inheritThreadLocals 一般情况都是 true
  			//如果父线程有可继承的变量,则赋值给当前线程
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

后面的 createInheritedMap 方法的细节这里就不过多赘述了,主要积就是将父线程中所有可继承的变量赋值给子线程。可以发现,思路都是一致的,就是提前预存父线程中的数据。

欢迎关注公众号
​​​​​​在这里插入图片描述

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