InheritableThreadLocal使用详解

一、InheritableThreadLocal的使用

InheritableThreadLocal用于子线程继承父线程的数值。将通过重写initialValue() 与childValue(Object parentValue)两个方法来展示例子。
其中initialValue()是InheritableThreadLocal类继承于ThreadLocal类的,用于初始化当前线程私有初始值,childValue(Object parentValue)是InheritableThreadLocal类的,作用是继承父线程的初始值并且进一步处理。

使用示例


public class test {

    /**
     * 静态调用IhThreadLocal
     */
    private static test.IThreadLocal iThreadLocal = new test.IThreadLocal();

    /**
     * 父线程输出IhThreadLocalUse的值与分两次创建子线程查看内IhThreadLocalUse的值
     *
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {

        // 情况1:在父类初始化IhThreadLocal前子线程先执行子线程
        test.Ithread iThread1 = new test.Ithread();
        iThread1.start();
        Thread.sleep(1000);

        // 情况2:在父线程初始化IhThreadLocal后执行子线程
        for (int i = 0; i < 2; i++) {
            System.out.println(iThreadLocal.get());
        }
        test.Ithread iThread2 = new test.Ithread();
        iThread2.start();
    }

    /**
     * 继承InheritableThreadLocal方法,并且重写初始化线程数值方法与修改子线程数值方法
     */
    static class IThreadLocal extends InheritableThreadLocal {

        @Override
        protected Object initialValue() {
            return LocalDateTime.now();
        }

        @Override
        protected Object childValue(Object parentValue) {
            return "子线程获取父线程传递数据:" + parentValue;
        }
    }

    /**
     * 输出子线程的IhThreadLocalUse的值
     */
    static class Ithread extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println(iThreadLocal.get());
                Thread.sleep(100);
            }
        }
    }
}

输出结果:

2022-09-18T12:42:47.361
2022-09-18T12:42:47.361
2022-09-18T12:42:48.312
2022-09-18T12:42:48.312
子线程获取父线程传递数据:2022-09-18T12:42:48.312
子线程获取父线程传递数据:2022-09-18T12:42:48.312

结论

  1. 通过情况1和结果可以看出,子线程继承父线程值时,得父线程已经初始化过值后,否则子线程则自身调用initialValue()来初始化数值,并且不走childParent方法,此时与使用ThreadLocal(用于声明每个线程自身独有的值)无异。
  2. 子线程在父线程已经初始化值的情况下,不调用initiaValue()方法来初始化值,而是走childValue来返回数值,无论是否重写过该方法,因为该方法本身就是返回父线程的数值。下面是该方法的源码,可以看到是返回parentValue的值。

二、InheritableThreadLocal原理

为什么InheritableThreadLocal 的子线程可以继承父线程值?

InheritableThreadLocal的源代码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
 
    protected T childValue(T parentValue) {
        return parentValue;
    }
   
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

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

这个类继承了ThreadLocal,并且重写了getMap和createMap方法,区别就是将 ThreadLocal 中的 threadLocals 换成了 inheritableThreadLocals,这两个变量都是ThreadLocalMap类型,并且都是Thread类的属性。

下面就一步步来看下InheritableThreadLocal为什么能拿到父线程中的ThreadLocal值。
step1:InheritableThreadLocal获取值先调用了ThreadLocal的get方法,所以我们直接看看get方法都做了些啥。

public T get() {
	Thread t = Thread.currentThread();
	    ThreadLocalMap map = getMap(t);
	    if (map != null) {
	        ThreadLocalMap.Entry e = map.getEntry(this);
	        if (e != null) {
	            @SuppressWarnings("unchecked")
	            T result = (T)e.value;
	            return result;
	        }
	    }
	    return setInitialValue();
	}

从上面的代码可以看出,get方法和ThreadLocal中是一样的,唯一有区别的就是其中的getMap方法重写了,返回的是inheritableThreadLocals属性。这个属性也是一个ThreadLocalMap类型的变量。那么从这边就可以推断出来:肯定是在某处将父线程中的ThreadLocal值赋值到了子线程的inheritableThreadLocals中。

step2:在源代码中搜索哪些地方使用到了inheritableThreadLocals这个属性,最后找到Thread的init()代码


    /**
     * Initializes a Thread.
     *
     * @param g                   线程组
     * @param target              run() 方法被调用的对象
     * @param name                新线程的名称
     * @param stackSize           新线程所需的堆栈大小,或为零表示要忽略此参数。
     * @param acc                 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
     * @param inheritThreadLocals 如果 {@code true},从构造线程继承可继承线程局部变量的初始值
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        
        // 第一项inheritThreadLocals 是传进来的boolean值,重载时传的是true,
        // 第二项就是判断父线程中的inheritableThreadLocals属性是否为空,不为空的话
        // 两个条件同时满足,把父线程的inheritableThreadLocals复制给子线程
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        	// 则新创建出来的子线程的inheritableThreadLocals 变量就和父线程的inheritableThreadLocals 的内容一样了。
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
     
        this.stackSize = stackSize;
      
        tid = nextThreadID();
    }

三、InheritableThreadLocal和线程池搭配使用存在的问题

InheritableThreadLocal和线程池搭配使用时,可能得不到想要的结果,因为线程池中的线程是复用的,并没有重新初始化线程,InheritableThreadLocal之所以起作用是因为在Thread类中最终会调用init()方法去把InheritableThreadLocal的map复制到子线程中。由于线程池复用了已有线程,所以没有调用init()方法这个过程,也就不能将父线程中的InheritableThreadLocal值传给子线程。


public class test {

    private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        // 主线程第一次赋值
        inheritableThreadLocal.set("first set1");
        System.out.println("父线程第一次获取值:" + inheritableThreadLocal.get());

        executorService.submit(new Ithread());
        Thread.sleep(2000);

        inheritableThreadLocal.set("second set2");
        System.out.println("父线程第二次获取值:" + inheritableThreadLocal.get());

        executorService.submit(new Ithread());
        executorService.shutdown();

    }

    /**
     * 输出子线程的IhThreadLocalUse的值
     */
    static class Ithread extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            System.out.println("子线程值:" + inheritableThreadLocal.get());
        }
    }
}

结果:

父线程第一次获取值:first set1
子线程值:first set1
父线程第二次获取值:second set2
子线程值:first set1

可以看出,我们在父线程中第二次set并没有被第二次submit的线程get到。

四、解决线程池传递值问题,见TransmittableThreadLocal

TransmittableThreadLocal

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值