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

ThreadLocal的原理和实现,今天咱们看看下面几个问题:java

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

1、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
ide

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

2、TransmittableThreadLocal

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

 

 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());
                    }
                });
            }
        }));
    }
}
spa

运行结果:.net

 

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 能够实现主线程和线程池之间的复用传递,无论是不是已缓存的线程,均可以实现线程池线程之间的复用隔离效果。

3、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来完成。

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