父子线程和线程池如何实现threadLocal变量传递

Posted MyClass社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了父子线程和线程池如何实现threadLocal变量传递相关的知识,希望对你有一定的参考价值。

上一次我们看了ThreadLocal的原理和实现,今天我们看看下面几个问题:

1.多线程中父子线程,子线程如何获取父线程的变量?
2.主线程和线程池的线程本地副本变量如何实现复用隔离?

一、InheritableThreadLocal的使用

       多线程中父子线程,子线程如何获取父线程的变量?下面就是InheritableThreadLocal的示例:

public static void main(String[] args{
    InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
    java.lang.ThreadLocal threadLocal = new ThreadLocal();
    inheritableThreadLocal.set("inheritableThreadLocal-value");
    threadLocal.set("threadLocal-value");
    System.out.println("main thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
    System.out.println("main thread --- threadLocal:"+threadLocal.get());
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run(
{
            System.out.println("new thread --- inheritableThreadLocal:"+inheritableThreadLocal.get());
            System.out.println("new thread --- threadLocal:"+threadLocal.get());
        }
    });
    thread.start();
}

运行结果:

main thread --- inheritableThreadLocal:inheritableThreadLocal-value
main thread --- threadLocal:threadLocal-value
new thread --- inheritableThreadLocal:inheritableThreadLocal-value
new thread --- threadLocal:null

     上面例子可以看出inheritableThreadLocal可以在子线程获取值,但是threadLocal不可以。InheritableThreadLocal其实继承ThreadLocal,其中重写了几个方法来操作ThreadLocalMap。主线程创建新线程的时候会获取当前线程的inheritableThreadLocals,并且将当前线程的该变量赋值给inheritableThreadLocals。这里在子线程修改引用变量的值,父线程也是可以获取的。

二、TransmittableThreadLocal

        TransmittableThreadLocal 是Alibaba开源的封装类。解决线程池线程或者缓存线程框架的ThreadLocal复用传递问题,需配合 TtlRunnable 和 TtlCallable 使用。下面看看TransmittableThreadLocal如何实现主线程和线程池中线程ThreadLocal复用和隔离的。

 public static void main(String[] args{
    InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
    //ExecutorService装换,将runnable转为TtlRunnable,为了配合TransmittableThreadLocal使用,没有其他逻辑,可以理解和普通线程池一样
    ExecutorService pool =  TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(3));

    for(int i=0;i<5;i++) {
        int j = i;
        //赋值threadLocal
        inheritableThreadLocal.set("inheritableThreadLocal-value-"+j);
        transmittableThreadLocal.set("transmittableThreadLocal-value-"+j);
        pool.execute(new Thread(new Runnable() {
            @Override
            public void run(
{
                pool.execute(new Runnable() {
                    @Override
                    public void run(
{
                        System.out.println(Thread.currentThread().getName() + " : " + inheritableThreadLocal.get()+"-----"+transmittableThreadLocal.get());
                    }
                });
            }
        }));
    }
}

运行结果:

pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-0
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-3
pool-1-thread-1 : inheritableThreadLocal-value-0-----transmittableThreadLocal-value-4
pool-1-thread-2 : inheritableThreadLocal-value-1-----transmittableThreadLocal-value-1
pool-1-thread-3 : inheritableThreadLocal-value-2-----transmittableThreadLocal-value-2

结果分析:

  1. inheritableThreadLocal在主线程和线程池之间可以传递的,但是归还线程池的线程inheritableThreadLocal值是一直不变的,如果是新开辟的线程,则会传递。所以inheritableThreadLocal还是不能解决线程池和主线程之间的复用问题,因为缓存的线程会直接使用老的inheritableThreadLocal的值

  2. 可以看出transmittableThreadLocal 可以实现主线程和线程池之间的复用传递,不管是否是已缓存的线程,都可以实现线程池线程之间的复用隔离效果。

三、TransmittableThreadLocal源码

    首先看一下TransmittableThreadLocal的主要设计逻辑是什么:

1.首先需要使用辅助线程类TtlRunnable封装了Runnable执行逻辑;
2.然后TtlRunnable中会TransmittableThreadLocal.copy()获取当前父线程的Map<TransmittableThreadLocal<?>, Object>;
3.执行前会通过backupAndSetToCopied赋值给当前线程;
4.线程执行完run方法后,通过restoreBackup()再备份一遍。我想应该是防止你线程进行修改。

//构造TtlRunnable
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
    if (null == runnable) {
        return null;
    }
    if (runnable instanceof TtlRunnable) {
        if (idempotent) {
            //避免多余的装饰,并确保幂等性
            return (TtlRunnable) runnable;
        } else {
            throw new IllegalStateException("Already TtlRunnable!");
        }
    }
    return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
//构造函数
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    //1.这里将父线程的TransmittableThreadLocal变量值进行一次深拷贝
    this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
 * TtlRunnable的run函数
 */

@Override
public void run() {
    //2.获取当前父线程的 TransmittableThreadLocal对象
    Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
    if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
    //3.TransmittableThreadLocal对象赋值给线程池该执行线程的ThreadLocal,并且返回出来
    Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);

    try {
        //4.这里在正常执行线程,此时的线程已经有了最新的TransmittableThreadLocal对象
        //如果是纯InheritableThreadLocal,是没有任何效果的
        runnable.run();
    } finally {
        //6.最终将一开始的Map<TransmittableThreadLocal<?>, Object>恢复,防止你线程进行修改
        TransmittableThreadLocal.restoreBackup(backup);
    }
}

总结

      从上面几个例子中可以得出,父子线程之间想要传递线程本地变量需要依赖InheritableThreadLocal,此时ThreadLocal变量只能作用单独的线程。但是线程池等线程缓存类框架中,要想实现主线程和线程池之间实现复用隔离效果,则可以使用TransmittableThreadLocal来完成。

以上是关于父子线程和线程池如何实现threadLocal变量传递的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal 父子线程之间该如何传递数据?

ThreadLocal 搭配线程池使用造成内存泄漏的原因和解决方案

解决线程切换导致ThreadLocal值丢失

面试官:ThreadLocal 搭配线程池时为什么会造成内存泄漏?

transmittable-thread-local:解决线程池之间ThreadLocal本地变量传递的问题

ThreadLocal的进化——TransmittableThreadLocal