加强版ThreadLocal之阿里开源TransmittableThreadLocal学习

目录

一、ThreadLocal

二、InheritableThreadLocal

2.1、ITL可用情况:

2.2、ITL不可用情况:

2.3、ITL原理源码:

InheritableThreadLocal是ThreadLocal的子类,复写了几个方法:

三、阿里的TransmittableThreadLocal

3.1、TTL使用:

3.2、TTL源码解析:

3.3、线程池使用:


我们都知道ThreadLocal是可以在一个线程中当容器使用的局部变量,是线程隔离、线程安全的。

但是如果子线程要获取父线程的变量,便不太方便。比方在业务代码中,为了提高响应速度,将多个复杂、长时间的计算或调用过程异步进行,让主线程可以先进行其他操作。异步子线程可能会用到主线程ThreadLocal中的内容,比方链路追踪。

下面我们一步一步进入:

一、ThreadLocal

示例:

public class MyThread01 {

    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> parent = new ThreadLocal<String>();

        parent.set(Thread.currentThread().getName() + "=======hello,myThread00");

        new Thread(() -> {
            try {
                // 设置本线程变量
                parent.set(Thread.currentThread().getName() + "=======hello,myThread01");
                // dosomething
                Thread.sleep(3000);
                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());
                // 清除
                parent.remove();
                // do other thing
                //.....
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "thread-1").start();

        new Thread(() -> {
            try {
                // 设置本线程变量
                parent.set(Thread.currentThread().getName() + "=======hello,myThread02");
                // dosomething
                Thread.sleep(3000);
                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());
                // 清除
                parent.remove();
                // do other thing
                //.....
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "thread-2").start();

        System.out.println(Thread.currentThread().getName() + ":" + parent.get());
    }
}

运行结果:

main:main=======hello,myThread00
thread-1:thread-1=======hello,myThread01
thread-2:thread-2=======hello,myThread02

父子线程各是各的。

子线程能不能直接获取到父线程的?:

public static void main(String[] args) throws InterruptedException {
    ThreadLocal<String> parent = new ThreadLocal<String>();

    parent.set(Thread.currentThread().getName() + "=======hello,myThread");

    new Thread(() -> {
        try {
            // 使用线程变量
            System.out.println(Thread.currentThread().getName() + ":" + parent.get());
            // do other thing
            // .....
        } catch (Exception e) {
            e.printStackTrace();
        }
    }, "child-thread").start();
}

运行结果:

child-thread:null

子线程拿不到父线程的。

二、InheritableThreadLocal

2.1、ITL可用情况:

JDK提供了InheritableThreadLocal可以让子线程拿到父线程的:

示例:

public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> parent = new InheritableThreadLocal<String>();

        parent.set(Thread.currentThread().getName() + "=======hello,myThread");

        new Thread(() -> {
            try {
                // 使用线程变量
                System.out.println(Thread.currentThread().getName() + ":" + parent.get());
                // do other thing
                // .....
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "child-thread").start();
    }

运行结果:子线程中拿到了父线程的值。

child-thread:main=======hello,myThread

2.2、ITL不可用情况:

但是InheritableThreadLocal的继承性是在new Thread创建子线程时候在构造函数内把父线程内线程变量拷贝到子线程内部的

为了不在创建新线程耗费资源,我们一般会用线程池,线程池的线程会复用,那么线程中的ThreadLocal便不对了,可能是旧的,因为线程是旧的。

如下:代码2向线程池投递3任务,这时候线程池内2个核心线程会被创建,并且队列里面有1个元素。然后代码3休眠4s,旨在让线程池避免饱和执行拒绝策略,然后代码4设置线程变量,代码5提交任务到线程池。运行输出:parent:null,子线程内访问不到父线程设置变量。

// 0.创建线程池
    private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(1));

    public static void main(String[] args) throws InterruptedException {

        // 1 创建线程变量
        InheritableThreadLocal<String> parent = new InheritableThreadLocal<String>();

        // 2 投递三个任务,让线程池中的线程全部创建。没有这段则可以拿到。
        for (int i = 0; i < 3; ++i) {
            bizPoolExecutor.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 3休眠4s
        Thread.sleep(4000);
        // 4.设置线程变量
        parent.set("value-set-in-parent");

        // 5. 提交任务到线程池
        bizPoolExecutor.execute(() -> {
            try {
                // 5.1访问线程变量
                System.out.println("parent:" + parent.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

运行结果:

parent:null

如果没有2那段代码,则可以拿到结果:

parent:value-set-in-parent

2.3、ITL原理源码:

ThreadLocal 实际上是 Thread 中保存的一个 ThreadLocalMap 类型的属性,与Thread搭配使用。

InheritableThreadLocal 也是如此。

public class Thread implements Runnable {
    // 如果单纯使用 ThreadLocal,则 Thread 使用该属性值保存 ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;
        // 否则使用该属性值
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  
    //inheritThreadLocals默认true
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
          Thread parent = currentThread();
          …………
          if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                //Thread的init方法创建新线程时把父线程的拿了过来
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
 
      }
}

InheritableThreadLocal是ThreadLocal的子类,复写了几个方法:

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中对应的方法:

public class ThreadLocal<T> {
……
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }
……
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
……
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
……
}

InheritableThreadLocal通过复写方法,主线程new InheritableThreadLocal<String>(),使Thread中的inheritableThreadLocals不为空,且getMap获取ThreadLocalMap时返回inheritableThreadLocals。childValue 方法用作从父线程中获取值。

则去new Thread创建子线程时,主线程当前线程的parent.inheritableThreadLocals != null,且将主线程的拿了过来

三、阿里的TransmittableThreadLocal

3.1、TTL使用:

添加pom:

<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>transmittable-thread-local</artifactId>
			<version>2.12.0</version>
		</dependency>

示例:1和5处代码变化,添加6。把具体任务使用TtlRunnable.get(task)包装了下,然后再提交到线程池。

// 0.创建线程池
    private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(1));

    public static void main(String[] args) throws InterruptedException {

        // 1 创建线程变量
        ThreadLocal<String> parent = new TransmittableThreadLocal<String>();

        // 2 投递三个任务,让线程池中的线程全部创建。
        for (int i = 0; i < 3; ++i) {
            bizPoolExecutor.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 3休眠4s
        Thread.sleep(4000);
        // 4.设置线程变量
        parent.set("value-set-in-parent");

        // 5. 提交任务到线程池
        Runnable task = () -> {
            try {
                // 5.1访问线程变量
                System.out.println("parent:"+parent.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        // 6、额外的处理,生成修饰了的对象ttlRunnable
        Runnable ttlRunnable = TtlRunnable.get(task);
        bizPoolExecutor.execute(ttlRunnable);
    }

运行结果:

parent:value-set-in-parent

TransmittableThreadLocal完美解决了线程变量继承问题,其是淘宝技术部 哲良开源的一个库,github地址为:https://github.com/alibaba/transmittable-thread-local

3.2、TTL源码解析:

在线程池场景下我们的需求:

InheritableThreadLocal的是在new Thread时候把父线程的inheritableThreadLocals复制到了子线程,从而实现线程变量的继承特性。而现在我们需要在提交任务到线程池前,把父线程中的线程变量保存到任务内,然后等线程池内线程执行任务前把保存的父线程的线程变量复制到线程池中的执行线程上,然后运行我们的任务,等任务运行完毕后,在清除掉执行线程上的线程变量。

可知第一我们需要包装提交到线程池内的任务,里面添加一个变量来保存父线程的线程变量。TransmittableThreadLocal就是这样的思路。

如上代码6就是把我们的任务使用TtlRunnable.get(task);包装了下,其内部就是拷贝父线程中的线程变量到包装的任务内保存起来。TtlRunnable.get(task)的get方法最后调到了:

    private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {	
        this.capturedRef = new AtomicReference<Object>(capture());	
        this.runnable = runnable;	
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;	
    }

参考:https://blog.csdn.net/m0_37055174/article/details/100179609

原理:

1、TTL继承自InheritableThreadLocal。
2、通过一个Holder,保存了每个线程当前持有的所有ThreadLocal对象。
3、用TtlRunnable的get方法来包裹一个Runnable对象,包裹对象时,会采用类似SNAPSHOT,快照的机制,通过Holder,捕获父线程当前持有的所有ThreadLocal。随后,子线程启动,在Runnable对象执行run方法之前,从Holder中取出先前捕获到的父线程所持有的ThreadLocal对象,并设置到当前子线程当中,设置之前会保存子线程原有的ThreadLocal作为backUp,当子线程执行结束后,通过backUp恢复其原有的ThreadLocal。

3.3、线程池使用:

抽取公共ThreadLocal容器类:

public final class MyThreadLocalContext {

    private MyThreadLocalContext() {}

    private static final ThreadLocal<String> parent = new TransmittableThreadLocal<String>();

    public static String get() {
        return parent.get();
    }

    public static void set(String data) {
        parent.set(data);
    }

    public static void remove() {
        parent.remove();
    }
}

子线程类和主线程通过公共容器类操作即可。

还有其他功能:


版权声明:本文为itwxming原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。