阿里TTL(TransmittableThreadLocal)分析

阿里TTL(TransmittableThreadLocal)分析

GITHUB:alibaba/transmittable-thread-local
ThreadLocal系列(一)-ThreadLocal的使用及原理解析

继承关系:ThreadLocal<T> <- InheritableThreadLocal<T> <- TransmittableThreadLocal<T>

阿里的TTL是扩展自java的ThreadLocal体系,因此想要了解TTL的功能需要先对继承的java父类功能有了解。
下面分别讲讲 ThreadLocal 、 InheritableThreadLocal。然后再介绍 TransmittableThreadLocal。

ThreadLocal

1. 介绍

ThreadLocal 类提供线程本地(局部)变量。每个线程都有自己独立初始化的变量副本。
TheadLocal 允许我们存储仅由特定线程访问的数据

使用场景:

  1. 每个线程持有自己的数据变量,如:
    /**
     * 每个线程拥有自己的Integer变量,默认初始化为0
     */
    private static final ThreadLocal<Integer> INTEGER_THREAD_LOCAL = ThreadLocal.withInitial(() -> 0);
  1. 避免数据竞争,每个线程持有自己的线程变量,如:
    /**
     * 每个线程拥有自己的SDF,避免竞争,保证线程安全
     */
    private static final ThreadLocal<SimpleDateFormat> SDF_TL = ThreadLocal.withInitial(() -> {
        return new SimpleDateFormat("yyyyMMddHHmmss");
    });
  1. 数据传递,跨方法级别数据传递
    /**
     * 数据传递上下文: LogbackMDCAdapter MDC 实际功能实现
     */
    final ThreadLocal<Map<String, String>> context = new ThreadLocal<>();

2. 方法

  1. get() 获取线程本地变量,如果没有,则调用initialValue() 初始化并设置。
    public T get() {
        Thread t = Thread.currentThread();
        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();
    }
  1. 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);
    }
  1. remove() 移除线程本地变量
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

3.实现原理

ThreadLocal实现主要依赖
1.ThreadLocal实例对象,作为线程访问数据的KEY存在。
2.ThreadLocalMap对象,每个线程有自己的map,ThreadLocal实例作为KEY。
注意:此处entry中的ThreadLocal key使用弱引用,防止内存泄漏,在清除方法中会清除所有 key为null的entry。

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLoclMap作为每个线程实例的字段存储在线程实例中:

    public class Thread implements Runnable {

        /**
         * 与此线程相关的ThreadLocal值。此Map由 ThreadLocal 类维护。
         */
        ThreadLocal.ThreadLocalMap threadLocals = null;

        /**
         * 与此线程相关的 InheritableThreadLocal 值。此映射由 InheritableThreadLocal 类维护
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    }

关系示意图:

InheritableThreadLocal

1. 介绍

public class InheritableThreadLocal<T> extends ThreadLocal<T>
InheritableThreadLocal 可继承的ThreadLocal,继承并扩扩展了原始ThreadLocal的功能,提供从父线程到子线程的value继承:创建子线程时,子线程接收父线程具有的 inheritable thread-local 值。

使用场景:

  1. 新建子现成需要集成父线程中的线程本地变量时,可以使用 InheritableThreadLocal 实现:
    /**
     * 可继承ThreadLocal,子线程将继承父线程中ITL变量value
     */
    private static final ThreadLocal<Integer> BIZ_ITL = new InheritableThreadLocal();

    /**
     * 运行结果:
     * main-thread-get:123
     * sub-thread-get:123
     */
    public static void main(String[] args) {
        BIZ_ITL.set(123);
        System.out.println("main-thread-get:"+BIZ_ITL.get());
        new Thread(()->{
            System.out.println("sub-thread-get:"+BIZ_ITL.get());
        }).start();
    }

2. 方法

  1. getMap() createMap() 使用ITLMap,非 TLMap。
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

3.实现原理

子程继承父线程 现成本地变量值 是在Thread创建时,copy父类现成中的 ITLMap中的key和Value:
Thread构造函数:调用init方法,在init方法中子线程根据父线程的ITLMap创建自己的ITLMap(传递、继承)。
需要注意:这里子现成中的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) {
        
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        
    }

3. TransmittableThreadLocal 组件

1.介绍

ThreadLocal和InheritableThreadLocal 能够完成变量的线程本地化和父子线程中的value传递。
但是现实项目中大多数线程池化在线程池中,因此,提交任务的线程无法将 提交现成的本地变量传递给执行task的任务线程。
TTL组件功能:在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
业务期望:上下文生命周期的操作从业务逻辑中分离出来。业务逻辑不涉及生命周期,就不会有业务代码如疏忽清理而引发的问题了。
整个上下文的传递流程或说生命周期可以规范化成:
捕捉、回放和恢复这3个操作,即CRR(capture/replay/restore)模式
CRR :

  1. capture方法:抓取线程(线程A)的所有TTL值。
  2. replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
  3. restore方法:恢复线程B执行replay方法之前的TTL值(即备份)

DEMO演示代码实例:

// ===========================================================================
// 线程 A
// ===========================================================================

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================

// (2) 在线程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
final Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
    // 你的业务逻辑,这里你可以获取到外面设置的TTL值
    String value = context.get();

    System.out.println("Hello: " + value);
    ...
    String result = "World: " + value;
} finally {
    // (3) 恢复线程 B执行replay方法之前的TTL值(即备份)
    TransmittableThreadLocal.Transmitter.restore(backup);
}

2.方法

1.TransmittableThreadLocal.java

  1. 关键成员组件:holder 记录线程中有哪些TTL需要被传输
    /**
     * 1. holder 本身是一个InheritableThreadLocal
     * 2. holder 的value是 WeakHashMap,map中的key是 TransmittableThreadLocal ,value 是null。相当于做set使用。
     */
    private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
        new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
            @Override
            protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
                return new WeakHashMap<>();
            }

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

    private void addThisToHolder() {
        if (!holder.get().containsKey(this)) {
            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
        }
    }

    private void removeThisFromHolder() {
        holder.get().remove(this);
    }
  1. get、set、remove操作实际除了操作ThreadLocal的变量之外,还在向holder中操作TTL
    public final T get() {
        T value = super.get();
        if (disableIgnoreNullValueSemantics || value != null) addThisToHolder();
        return value;
    }

    public final void set(T value) {
        if (!disableIgnoreNullValueSemantics && value == null) {
            // may set null to remove value
            remove();
        } else {
            super.set(value);
            addThisToHolder();
        }
    }

    public final void remove() {
        removeThisFromHolder();
        super.remove();
    }

2.transmit

关于构词后缀er与ee的说明:

  • transmit是动词传递,transmitter动作的执行者/主动方,而transmittee动作的接收者/被动方。
  • er与ee后缀的常见词是employer(雇主)/employee(雇员)、caller(调用者)/callee(被调用者)。
    Transmitter 用户实现 CRR 的具体操作,依赖于holder中记录的数据

具体实现为:Transmitter(传递,静态工具方法,聚合调用Transmittee实例的CRR方法) && Transmittee(被传递,每一个被传送对象的具体实现实例化,实现具体的CRR方法)

分析: ttlTransmittee 实例化实现:


        private static final Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>> ttlTransmittee =
                new Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>>() {
                    //抓取提交线程的TTL和value
                    @Override
                    public HashMap<TransmittableThreadLocal<Object>, Object> capture() {
                        final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = newHashMap(holder.get().size());
                        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
                            ttl2Value.put(threadLocal, threadLocal.copyValue());
                        }
                        return ttl2Value;
                    }

                    //重放提交线程抓取到的TTL和value
                    @Override
                    public HashMap<TransmittableThreadLocal<Object>, Object> replay(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
                        final HashMap<TransmittableThreadLocal<Object>, Object> backup = newHashMap(holder.get().size());

                        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
                            TransmittableThreadLocal<Object> threadLocal = iterator.next();

                            // backup
                            backup.put(threadLocal, threadLocal.get());

                            // clear the TTL values that is not in captured
                            // avoid the extra TTL values after replay when run task
                            if (!captured.containsKey(threadLocal)) {
                                iterator.remove();
                                threadLocal.superRemove();
                            }
                        }

                        // set TTL values to captured
                        setTtlValuesTo(captured);

                        // call beforeExecute callback
                        doExecuteCallback(true);

                        return backup;
                    }

                    //清除TTL和value
                    @Override
                    public HashMap<TransmittableThreadLocal<Object>, Object> clear() {
                        return replay(newHashMap(0));
                    }
                    //还原运行线程之前备份的TTL和value
                    @Override
                    public void restore(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
                        // call afterExecute callback
                        doExecuteCallback(false);

                        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
                            TransmittableThreadLocal<Object> threadLocal = iterator.next();

                            // clear the TTL values that is not in backup
                            // avoid the extra TTL values after restore
                            if (!backup.containsKey(threadLocal)) {
                                iterator.remove();
                                threadLocal.superRemove();
                            }
                        }

                        // restore TTL values
                        setTtlValuesTo(backup);
                    }
                };

        private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
            for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
                TransmittableThreadLocal<Object> threadLocal = entry.getKey();
                threadLocal.set(entry.getValue());
            }
        }

3.实现原理

如何实现从提交线程向运行线程传输TTL和value

  1. TransmittableThreadLocal组件 有3种使用方式:
  1. 修饰Runnable和Callable
  2. 修饰线程池
  3. 使用Java Agent来修饰JDK线程池实现类

1. 修饰Runnable和Callable

实际为Wrapper包装原始 task,在其运行前后执行CRR操作,保证task运行时的TTL上下文于提交线程相同


    /**
     * 包装原始task为 TTl Wrapper
     */
    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
        //1. 抓取提交线程的TTL和value
        this.capturedRef = new AtomicReference<>(capture());
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }

    /**
     * wrap method {@link Runnable#run()}.
     */
    @Override
    public void run() {
        final Object captured = capturedRef.get();
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
        //2. 回放提交线程的TTL和value
        final Object backup = replay(captured);
        try {
            runnable.run();
        } finally {
            //3. 回放task运行线程的备份TTL和value
            restore(backup);
        }
    }

整个过程的完整时序图

2. 修饰线程池

TtlExecutors 工具类提供了任务提交时的task包装功能,业务调用方无需感知具体增强的task逻辑

    ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) {
        this.executor = executor;
        this.idempotent = idempotent;
    }

    @Override
    public void execute(@NonNull Runnable command) {
        // 重写原有任务提交方法,使用包装后的TTl 任务
        executor.execute(TtlRunnable.get(command, false, idempotent));
    }

3. 使用Java Agent来修饰JDK线程池实现类

Java Agent方式对应用代码无侵入
已有Java Agent中嵌入TTL Agent 代码样例

import com.alibaba.ttl.threadpool.agent.TtlAgent;
import com.alibaba.ttl.threadpool.agent.TtlTransformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.logging.Logger;

public final class YourXxxAgent {
    private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName());

    public static void premain(String agentArgs, Instrumentation inst) {
        TtlAgent.premain(agentArgs, inst); // add TTL Transformer

        // add your Transformer
        ...
    }
}
posted @ 2023-10-10 20:44  gsanye  阅读(4910)  评论(0)    收藏  举报