开发者社区 > 博文 > 从TL、ITL到TTL
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

从TL、ITL到TTL

  • jd****
  • 2023-07-14
  • IP归属:北京
  • 8400浏览

    1、概述

    ThreadLocal(TL)是Java中一种线程局部变量实现机制,他为每个线程提供一个单独的变量副本,保证多线程场景下,变量的线程安全。经常用于代替参数的显式传递。

    InheritableThreadLocal(ITL)是JDK提供的TL增强版,而TransmittableThreadLocal(TTL)是阿里开源的ITL增强版

    这些ThreadLocal在不同场景下有不同用途,我们来分析一下:

    2、ThreadLocal

    ThreadLocal主要的方法有四个:initialValue、set、get、remove

    2.1、初始化——initialValule

    当线程首次访问该ThreadLocal时(ThreadLocal.get()),会进行初始化赋值。我们常用两种方法初始化ThreadLocal

    2.1.1、重写initialValue

    ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "";
        }
    };
    

    2.1.2、调用ThreadLocal.withInitial

    ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
    

    他会创建一个SuppliedThreadLocal内部类

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    

    该类重写了initialValue方法

    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    
        private final Supplier<? extends T> supplier;
    
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
    
        @Override
        protected T initialValue() {
            //当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化
            return supplier.get();
        }
    }
    

    ⚠️ITL并没有重新实现withInitial,如果使用withInitial则会创建STL,失去自己增强的特性

    2.2、赋值——set

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    这里出现了一个关键属性ThreadLocalMap,类定义在ThreadLocal中,是Thread的成员变量

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

    ThreadLocalMap内部还有一个内部类Entry,是存值的地方

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                //ThreadLocal的引用是“key”
                super(k);
                //线程局部变量是value
                value = v;
            }
        }
        //Entry数组
        //value具体放在哪个index下,是由ThreadLocal的hashCode算出来的
        private Entry[] table;
    }
    

    2.3、取值——get

    public T get() {
        Thread t = Thread.currentThread();
        //1、获取线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //2、根据ThreadLocal的hashCode,获取对应Entry下的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //3、如果没有赋过值,则初始化
        return setInitialValue();
    }
    

    2.4、清空——remove

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             //会将对应Entry、包括他的key、value手动置null
             m.remove(this);
     }
    

    3、InheritableThreadLocal

    3.1、TL在父子线程场景下存在的问题

    我们先来看一个例子

    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "A");
        threadLocal.set("B");
        Thread thread = new Thread(() -> {
            System.out.println("子线程ThreadLocal:" + threadLocal.get());
        }, "子线程");
        thread.start();
        thread.join();
    }
    

    打印结果如下,可见子线程的ThreadLocal是初始值,并没有使用父线程修改后的值:

    子线程ThreadLocal:A
    

    线程的ThreadLocalMap是首次访问时创建的,所以子线程使用ThreadLocal的时候,会初始化一个新的ThreadLocal,线程局部变量为默认值

    ⚠️所以,TL不具有遗传性

    3.2、ITL的解决方案

    为了解决TL子线程遗传性的问题,JDK引入了ITL

    他继承ThreadLocal,重写了childValue、getMap、createMap三个方法

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

    这里出现了inheritableThreadLocals,他存储的就是从父线程拷贝过来的ThreadLocal,这个值是在父线程首次修改ThreadLocal的时候赋值的,然后在子线程创建时拷贝过来的

    //父线程部分:
    public void set(T value) {
        Thread t = Thread.currentThread();
        //该方法被ITL重写,访问inheritableThreadLocals为null
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        //该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals
            createMap(t, value);
    }
    
    //子线程部分:
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
        //省略一些代码...
    
        //获取当前线程(父线程、也就是创建子线程的线程)
        Thread parent = currentThread();
        //1、允许ThreadLocal遗传(这个默认为true)
        //2、inheritableThreadLocals不为空,因为父线程调用set了
        //父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    
    //createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
        //深拷贝父线程ThreadLocalMap
        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    //childValue被ITL重写,返回父线程ThreadLocal的值
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
    

    使用ITL的效果

    public static void main(String[] args) throws InterruptedException {
            ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
                @Override
                protected String initialValue() {
                    return "A";
                }
            };
            threadLocal.set("B");
            Thread thread = new Thread(() -> {
                System.out.println("子线程ThreadLocal:" + threadLocal.get());
            }, "子线程");
            thread.start();
    
            thread.join();
    }
    

    打印结果如下,子线程拷贝了父线程ThreadLocal:

    子线程ThreadLocal:B

    总结一下,ITL解决父子线程遗传性的核心思路是,将可遗传的ThreadLocal放在父线程新的ThreadLocalMap中,在子线程首次使用时进行拷贝

    4.、TransmittableThreadLocal

    4.1、ITL在线程复用场景下存在的问题

    我们再从一个简单的例子说起

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
            @Override
            protected String initialValue() {
                return "A";
            }
        };
        threadLocal.set("B");
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        //1、子线程第一次获取ThreadLocal
        executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
        Thread.sleep(1000);
        //2、父线程修改ThreadLocal
        threadLocal.set("C");
        System.out.println("父线程修改ThreadLocal为"+threadLocal.get());
        //3、子线程第二次获取ThreadLocal
        executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
    }
    

    打印结果如下,子线程在第二次打印时,并没有拷贝父线程的ThreadLocal,使用的还是首次拷贝的值:

    子线程ThreadLocal:B
    父线程修改ThreadLocal为C
    子线程ThreadLocal:B

    ⚠️可复用的子线程不会感知父线程ThreadLocal的变化

    4.2、TTL的解决方案

    4.2.1、TTL的使用

    TTL在ITL上做了稍微复杂的封装,我们从使用开始了解

    引入依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <version>latest</version>
    </dependency>
    

    在使用TTL时,线程需要经过TTL封装,线程池同理

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadLocal<String> threadLocal = new TransmittableThreadLocal<String>() {
            @Override
            protected String initialValue() {
                return "A";
            }
        };
        threadLocal.set("B");
        ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
        executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
        Thread.sleep(1000);
        threadLocal.set("C");
        System.out.println("父线程修改ThreadLocal为" + threadLocal.get());
        executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
        Thread.sleep(1000);
        executorService.submit(() -> {
            threadLocal.set("D");
            System.out.println("子线程修改ThreadLocal为" + threadLocal.get());
        });
        Thread.sleep(1000);
        executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get()));
        Thread.sleep(1000);
    }
    

    打印结果如下,子线程每次都会获取父线程的ThreadLocal

    子线程ThreadLocal:B
    父线程修改ThreadLocal为C
    子线程ThreadLocal:C
    子线程修改ThreadLocal为D
    子线程ThreadLocal:C

    从使用上看,TTL要求将任务封装,那我们就从ThreadLocal和ExecutorService两部分入手

    4.2.2、TTL对ThreadLocal的封装

    下面是TTL的取值和赋值逻辑,都涉及一个关键方法addThisToHolder,对应的属性holder会在线程池执行任务时用到

    //TransmittableThreadLocal.addThisToHolder()
    private void addThisToHolder() {
        //InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
        if (!holder.get().containsKey(this)) {
            //holder是静态变量,他会把TTL存到当前线程的map中
            //value是null,他其实是把Map当Set用
            //主线程赋值时,会获取主线程的holderMap,然后把TTL存进去
            holder.get().put((TransmittableThreadLocal<Object>) this, null);
        }
    }
    
    @Override
    public final void set(T value) {
        if (!disableIgnoreNullValueSemantics && null == value) {
            remove();
        } else {
            super.set(value);
            //当主线程赋值时,会将自己的TTL放到自己的map中
            addThisToHolder();
        }
    }
    
    @Override
    public final T get() {
        T value = super.get();
        if (disableIgnoreNullValueSemantics || null != value) 
            addThisToHolder();
        return value;
    }
    

    4.2.3、TTL对任务的封装

    //我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装
    public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
        if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
            return executorService;
        }
        //入参是线程池,通过包装类代理线程池的操作
        return new ExecutorServiceTtlWrapper(executorService);
    }
    
    //ExecutorServiceTtlWrapper.submit()
    public Future<?> submit(@NonNull Runnable task) {
        //将提交的任务进行封装
        return executorService.submit(TtlRunnable.get(task));
    }
    

    4.2.3.1、任务构建

    TtlRunnable构造方法

    这里都是主线程在操作,因为任务是主线程提交的

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

    这里有一个关键属性capturedRef,他是一个原子引用,存了TTL

    //TrasmitableThreadLocal.Transmitter
    public static Object capture() {
        //获取ttl的值构建快照
        return new Snapshot(captureTtlValues(), captureThreadLocalValues());
    }
    
    private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
        HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
            //将主线程TTL的值存到当前任务中
            ttl2Value.put(threadLocal, threadLocal.copyValue());
        }
        return ttl2Value;
    }
    

    4.2.3.2、任务执行

    任务执行的代码如下,在任务执行前回放ThreadLocal,在任务执行后恢复ThreadLocal:

    这里都是子线程在操作,因为任务都是子线程执行的

    @Override
    public void run() {
        Object captured = capturedRef.get();
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //1、备份子线程ThreadLocal
        //2、使用主线程提交任务时构建的ThreadLocal副本,将子线程ThreadLocal覆盖
        Object backup = replay(captured);
        try {
            //3、任务执行
            runnable.run();
        } finally {
            //3、使用之前备份的子线程ThreadLocal进行恢复
            restore(backup);
        }
    }
    

    总结一下,TTL让子线程感知父线程变化的核心思路是,主线程在任务提交时构建ThreadLocal副本,在子线程执行任务时供其使用

    ⚠️提交和执行任务会对TTL进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略

    TTL官方性能测试



    文章数
    1
    阅读量
    210

    作者其他文章