TransmittableThreadLocal的作用和原理

作用

在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
最好先看下官方介绍
和threadLocal、InheritableThreadLocal的区别请转至 ThreadLocal父子线程传递实现方案.

简单使用

public class TtlTest {

    static TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        parent.set("parent value");
        Runnable task = new Task();
// 额外的处理,生成修饰了的对象ttlRunnable
//        Runnable ttlRunnable = TtlRunnable.get(task);
        executorService.submit(TtlRunnable.get(task));
        TimeUnit.SECONDS.sleep(1);
        executorService.submit(TtlRunnable.get(task));
        TimeUnit.SECONDS.sleep(1);
// Task中可以读取, 值是"parent value"
        String value = parent.get();
        System.out.println("out value: " + value);
    }
    
    public static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println("=======");
            String value1 = parent.get();
            System.out.println("in value1: " + value1);
            parent.set("child value");
            String value2 = parent.get();
//            System.out.println("in value2" + value2);
        }
    }
}

以上实例中如果使用其他threadLocal代替TransmittableThreadLocal,则后续的task线程里parent.get()读到的都会是child value,读取不到父线程的parent value。
那么如何实现的呢

原理

这里先简述下threadLocal 和 thread,thread里的threadLocalMap存储threadLocal和对应值的集合, 一个thread里可以存在多个threadLocal,一个threadLocal可以存在多个thread中

先看下TransmittableThreadLocal的重要属性

  // Note about holder:
    // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*).
    // 2. The type of value in holder is WeakHashMap<TransmittableThreadLocal<Object>, ?>.
    //    2.1 but the WeakHashMap is used as a *Set*:
    //        - the value of WeakHashMap is *always null,
    //        - and never be used.
    //    2.2 WeakHashMap support *null* value.
    private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
            new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
                }

                @Override
                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
                }
            };

static修饰的全局的holder弱引用(防内存泄漏),initialValue初始化时使用,childValue异步时父线程的属性是直接作为初始化值赋值给子线程的本地变量对象
下面来跟一下简单实用中的第一个set方法

 1. parent.set("parent value");

 2. public final void set(T value) {
        if (!disableIgnoreNullValueSemantics && null == value) {
            // may set null to remove value
            remove();
        } else {
        // **第一步先将TransmittableThreadLocal<String> parent和值”parent value“存入当前thread的inheritableThreadLocals**
            super.set(value);
         // 存入holdor
            addThisToHolder();
        }
    }
 3.  // 存入holdor 
   private void addThisToHolder() {
        if (!holder.get().containsKey(this)) {
            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
        }
    }
 4.  // holder.get()
 public final T get() {
 // 从当前线程thread中获取
        T value = super.get();
        if (disableIgnoreNullValueSemantics || null != value) addThisToHolder();
        return value;
    }
 5. // super.get()
	public T get() {
        Thread t = Thread.currentThread();
        // 这里的ThreadLocalMap在上述3中super.set(value)已经创建,但还没有holder的属性
        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();
    }
 6. //setInitialValue()
	private T setInitialValue() {
	// 这里的init调用了holder定义里的initialValue方法,然后存入map中
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

这时候线程里的ThreadLocalMap有两个对象,属性如下,同时由于TTL继承了ITL,所以在线程池创建线程的时候会把主线程如下的inheritableThreadLocals传递给创建的子线程(见thread.init方法)。
在这里插入图片描述
下面看下另一个重要的类TtlRunnable,装饰器模式对runnable进行了装饰

private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
   // capture 从holder中获取当前**主线程**的变量,供后续使用,子线程的变量在第一次创建线程的时候就已经初始化了(上面已经提到了)
        this.capturedRef = new AtomicReference<Object>(capture());
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }

public void run() {
// 获取捕捉到的线程中的threadLocalMap
        Object captured = capturedRef.get();
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
 		/**
         * 重点方法replay,暂存当前子线程的threadLocalMap到backup(上面提到的初始化时获取的threadLocalMap),给当前子线程赋值之前捕获到的父线程里的threadLocalMap(父线程可能会对threadLocal有所跟新,这里是为了获取捕获时最新的线程变量)
         */
        Object backup = replay(captured);
        try {
            runnable.run();
        } finally {
        // 恢复暂存的数据到子线程,恢复线程执行时被改变的threadLocal对应的值
            restore(backup);
        }
    }

那么究竟是哪一步影响了上面例子的结果呢,restore(backup)将原本的“parent value”重新赋值到线程变量,尽管run方法中对这个线程变量的属性做了改变。

整个完整的时序图
在这里插入图片描述

总结:

TransmittableThreadLocal主要解决两个问题

  1. 父线程新增线程变量的传递。TTL继承了ITL所以在创建子线程时会从父线程拷贝,但是线程复用,后续不会再次拷贝,线程变量发生变更需要从主线程同步,TtlRunnable每次创建时同步。
  2. 线程执行完后,线程变量的恢复。无论线程变量是在主线程发生变更,还是子线程里发生变更,最终都要恢复到线程最初的值。子线程最初的线程变量暂存在backup。

题外话:

1、Thread里的ThreadLocalMap里的threadLocal是弱引用,在gc后会被回收吗?

public class ThreadLocalLeakTest {
    static ThreadLocal<String> LOCAL = new ThreadLocal<String>();

    /**
     * Thread里的ThreadLocalMap里的threadLocal是弱引用,当前LOCAL是自己new出来的,是强引用,
     * 两个引用变量指向一个存在于内存中的ThreadLocal对象的实例,由于存在强引用,所以弱引用在gc时也不会回收对象,
     * 但当强引用变量指向内存中其他的对象实例时,或者置为null时,只存在弱引用的threadLocal,会在gc时被回收掉
     */
    public static void main(String[] args) {
        LOCAL.set("测试ThreadLocalMap弱引用自动回收");
        Thread thread = Thread.currentThread();
//        LOCAL = null;
        LOCAL = new ThreadLocal<String>();
        System.gc();
        System.out.println("");
    }
}

2、TTL里的WeakHashMap原理和回收策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值