TransmittableThreadLocal源码分析

1. 类继承关系

类继承关系

2. 时序图

时序图直接用作者画的,非常详细


TransmittableThreadLocal-sequence-diagram.png

3. 流程说明

1. createTtl 这一步最重要的就是初始化了holder

private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
        new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
            @Override
            protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
                return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
            }
            @Override
            protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
                return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
            }
        };

holder的两个重写方法说明:

  • initialValue调用的时机是在holder.get()的时候如果当前线程的ThreadLocalMap为空,则调用这个方法初始化,见单测代码 TtlRunnableTest.test_ttlRunnable_inSameThread
  • childValue调用的时机是子线程初始化的时候 见单测代码 TtlRunnableTest.test_ttlRunnable_asyncWithNewThread
    holder可以说是ttl最核心的变量之一了,理解了这个变量状态的流转,就理解了这个框架。为啥使用WeakHashMap应该是为了内存回收♻️

2. setTtlValue 这一步主要做了两件事

(1)先在ttl的父类ThreadLocal设置该线程的值

@Override
public final void set(T value) {
​    // 先调用ThreadLocal的set方法
    super.set(value);
    if (null == value) { // may set null to remove value
        removeValue();
    } else {
        addValue();
    }
}
​
//ThreadLocal 的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);
}​​​

(2) 往holder中添加当前的ttl

private void addValue() {
    if (!holder.get().containsKey(this)) {
        holder.get().put(this, null); // WeakHashMap supports null value.
        // System.out.println(Thread.currentThread().getName()+" "+holder.get()); //可以加下这个输出下加深理解
    }
}

(3) 调用ThreadLocal的get方法

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;
            }
        }
        //调用ttl的holder重写的initialValue方法
        return setInitialValue();
    }

所以有了如下的映射关系,通过当前thread获取获取holder中的keySet,通过遍历keySet获取ttl,通过ttl委托ThreadLocal维护变量的值
holder运行时的结构:

main {com.alibaba.Utils$newTtlInstanceAndPut$ttl$1@1fc2b765(parent-create-unmodified-in-child)=null}

3. createBizTaskRunnable

实现runnable接口, 自己的业务逻辑

4. createTtlRunnableWrapper(Runnable runnable)

这一步最重要的是把当前线程的threadLocal的值给拷贝到了capturedRef ,为啥用AtomicRef,需要原子更新操作TtlRunnable line47

4.1 TtlRunnable get(Runnable runnable)

//runnable 提交的业务runnable
//releaseTtlValueReferenceAfterRun 是否在value返回后释放,避免内存泄漏
//idempotent 是否需要幂等
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
        if (null == runnable) {
            return null;
        }

        if (runnable instanceof TtlRunnable) {
            if (idempotent) {
                // avoid redundant decoration, and ensure idempotency
                return (TtlRunnable) runnable;
            } else {
                throw new IllegalStateException("Already TtlRunnable!");
            }
        }
        return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
    }

4.2 创建新的TtlRunnable包装runnable

private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
        //该线程包含的所有ttl的变量
        //类型是 Map<TransmittableThreadLocal<?>, Object>
        this.capturedRef = new AtomicReference<Object>(capture());
        this.runnable = runnable;
        //是否需要在使用后释放
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }

4.3 获取当前线程所有的ttl的实例和对应的threadLocal的值

重点: capture的是父线程的ttl, 也就是创建TtlRunnable的线程,这个很重要很容易误解。

public static Object capture() {
            Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
            //holder.get().keySet()是当前线程使用的ttl
            //在set这一步 holder添加了ttl的关系,同时ttl中set了当前线程的value
            for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
                captured.put(threadLocal, threadLocal.copyValue());
            }
            return captured;
        }

5. submitTtlRunnableToThreadPool

提交任务给线程池

6. 线程池执行run方法

步骤6之后的逻辑已经和业务代码没有关系了

(1) TtlRunnable重写了Runnable的run方法

@Override
public void run() {
    //获取任务提交时,即包装runnable时的ttl
    Object captured = capturedRef.get();
    if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
    //执行前 获取backup
    Object backup = replay(captured);
    try {
        //执行run方法
        runnable.run();
    } finally {
        //通过backup恢复当前线程的ttl,避免run方法中修改了ttl
        //避免线程复用造成父线程的ttl丢失
        restore(backup);
    }
}

(2) replay(captured)方法

  • 声明两个局部变量 capturedMap和backup
  • 遍历holder.get().entrySet().iterator()
    • 将threadLocal的值放到backup变量
    • 如果capturedMap不包含holder中该线程的threadLocal的key,将holder中多余的threadLocal key remove, 将其父类的threadLocal变量移除,避免线程运行时获取ttl的干扰
public static Object replay(Object captured) {
            @SuppressWarnings("unchecked")
            Map<TransmittableThreadLocal<?>, Object> capturedMap = (Map<TransmittableThreadLocal<?>, Object>) captured;
            Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();

            for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
                 iterator.hasNext(); ) {
                Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
                TransmittableThreadLocal<?> threadLocal = next.getKey();

                // 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 (!capturedMap.containsKey(threadLocal)) {
                    iterator.remove();
                    threadLocal.superRemove();
                }
            }

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

            // call beforeExecute callback
            doExecuteCallback(true);

            return backup;
        }

将capturedMap的值拷贝到当前线程的threadLocal,并更新holder,这一步的精髓就是通过这个Map<TransmittableThreadLocal<?>, Object>类型的值将ttl从父线程往子线程中塞数据。类似的用法其实还有打破java双亲委托往threadContext中塞数据,典型应用就是jdbc的驱动的实现

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

doExecuteCallback 自定义扩展逻辑,可以做前置拦截

(3) 执行run方法

(4) 执行后 restore(backup)

将当前线程的ttl恢复到任务提交时

  • doExecuteCallback(false) 后置处理方法,用户可以自定义
  • 遍历holder.get().entrySet().iterator()
    • 如果backupMap不包含holder中该线程的threadLocal的key,将holder中多余的threadLocal key remove
  • setTtlValuesTo(backupMap)

4. 重点

  • 理解TransmittableThreadLocal的holder变量的流转

    • holder是一个InheritableThreadLocal静态变量, InheritableThreadLocal可以让threadLocal的值在子线程init的时候从父线程传递到子线程
    • holder其实维护了两层关系
      • 当前线程本地变量和ttl的映射关系
      • 任务提交时holder和所有提交时的ttl的映射关系
  • 状态流转图


    transmittable holder.png
  • 框架需要实现的目的是啥
    • 把 任务提交给线程池时的ThreadLocal值传递到 任务执行时

5. 学习的地方

  • 核心代码非常少, 王垠在博文《如何阅读别人的代码》说到的"造就我今天的编程能力和洞察力的,不是几百万行的大型项目,而是小到几行,几十行之短的练习。不要小看了这些短小的代码,它们就是编程最精髓的东西。反反复复琢磨这些短小的代码,不断改进和提炼里面的结构,磨砺自己的思维"。 个人觉得ttl框架精髓主要是holder变量的设计和维护,使用capturedRef实现父子(线程池)的ttl值传递,深刻理解ThreadLocal和InheritableThreadLocal的继承关系,以及Thread和ThreadLocalMap的关系。
  • 代码的注释非常的详细,比如方法的描述,参数的具体的意义,功能点是哪个版本之后才有的,当然作者的markdown的文档写的不错很生动
  • 用到了很多设计模式,比如模版方法模式,装饰器模式,委托模式等
  • 用到了类似AOP的概念 可以实现自己的前置和后置逻辑
  • 通过中间变量进行上层和下层的传递
  • 测试用例写的非常详尽,遇到不理解的可以通过单测来加深理解,算是我看过的非常不错的注释详尽的单测代码了👍
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355