线程池传递对象参数_通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

前提

最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文。想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能向预先创建的线程中进行变量传递的局限性,刚好有一位HSBC的技术大牛前同事提到了团队引入了transmittable-thread-local解决了此问题。借着这个契机,顺便clone了transmittable-thread-local源码进行分析,这篇文章会把ThreadLocal和InheritableThreadLocal的局限性分析完毕,并且从一些基本原理以及设计模式的运用分析transmittable-thread-local(下文简称为TTL)整套框架的实现。如果对线程池和ThreadLocal不熟悉的话,可以先参看一下前置文章:

  • 《JUC同步器框架AbstractQueuedSynchronizer源码图文分析》
  • 《JUC线程池ThreadPoolExecutor源码分析》
  • 《ThreadLocal源码分析-黄金分割数的使用》

这篇文章前后花了两周时间编写,行文比价干硬,文字比较多(接近5W字),希望带着耐心阅读。

父子线程的变量传递

在Java中没有明确给出一个API可以基于子线程实例获取其父线程实例,有一个相对可行的方案就是在创建子线程Thread实例的时候获取当前线程的实例,用到的API是Thread#currentThread():

1public class Thread implements Runnable {
2
3    // 省略其他代码
4
5    @HotSpotIntrinsicCandidate
6    public static native Thread currentThread();
7
8    // 省略其他代码
9}

Thread#currentThread()方法是一个静态本地方法,它是由JVM实现,这是在JDK中唯一可以获取父线程实例的API。一般而言,如果想在子线程实例中得到它的父线程实例,那么需要像如下这样操作:

1public class InheritableThread {
 2
 3    public static void main(String[] args) throws Exception{
 4        // 父线程就是main线程
 5        Thread parentThread = Thread.currentThread();
 6        Thread childThread = new Thread(()-> {
 7            System.out.println("Parent thread is:" + parentThread.getName());
 8        },"childThread");
 9        childThread.start();
10        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
11    }
12}
13// 输出结果:
14Parent thread is:main

类似地,如果我们想把一个父子线程共享的变量实例传递,也可以这样做:

1public class InheritableVars {
 2
 3    public static void main(String[] args) throws Exception {
 4        // 父线程就是main线程
 5        Thread parentThread = Thread.currentThread();
 6        final Var var = new Var();
 7        var.setValue1("var1");
 8        var.setValue2("var2");
 9        Thread childThread = new Thread(() -> {
10            System.out.println("Parent thread is:" + parentThread.getName());
11            methodFrame1(var);
12        }, "childThread");
13        childThread.start();
14        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
15    }
16
17    private static void methodFrame1(Var var) {
18        methodFrame2(var);
19    }
20
21    private static void methodFrame2(Var var) {
22
23    }
24
25    @Data
26    private static class Var {
27
28        private Object value1;
29        private Object value2;
30    }
31}

这种做法其实是可行的,子线程调用的方法栈中的所有方法都必须显示传入需要从父线程传递过来的参数引用Var实例,这样就会产生硬编码问题,既不灵活也导致方法不能复用,所以才衍生出线程本地变量Thread Local,具体的实现有ThreadLocal和InheritableThreadLocal。它们两者的基本原理是类似的,实际上所有的变量实例是缓存在线程实例的变量ThreadLocal.ThreadLocalMap中,线程本地变量实例都只是线程实例获取ThreadLocal.ThreadLocalMap的一道桥梁:

1public class Thread implements Runnable {
 2
 3    // 省略其他代码
 4
 5    // KEY为ThreadLocal实例,VALUE为具体的值
 6    ThreadLocal.ThreadLocalMap threadLocals = null;
 7
 8    // KEY为InheritableThreadLocal实例,VALUE为具体的值
 9    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
10
11    // 省略其他代码
12}

ThreadLocal和InheritableThreadLocal之间的区别可以结合源码分析一下(见下一小节)。前面的分析听起来如果觉得抽象的话,可以自己写几个类推敲一下,假如线程其实叫ThrowableThread,而线程本地变量叫ThrowableThreadLocal,那么它们之间的关系如下:

1public class Actor {
 2
 3    static ThrowableThreadLocal THREAD_LOCAL = new ThrowableThreadLocal();
 4
 5    public static void main(String[] args) throws Exception {
 6        ThrowableThread throwableThread = new ThrowableThread() {
 7
 8            @Override
 9            public void run() {
10                methodFrame1();
11            }
12        };
13        throwableThread.start();
14    }
15
16    private static void methodFrame1() {
17        THREAD_LOCAL.set("throwable");
18        methodFrame2();
19    }
20
21    private static void methodFrame2() {
22        System.out.println(THREAD_LOCAL.get());
23    }
24
25    /**
26     * 这个类暂且认为是java.lang.Thread
27     */
28    private static class ThrowableThread implements Runnable {
29
30        ThrowableThreadLocal.ThrowableThreadLocalMap threadLocalMap;
31
32        @Override
33        public void run() {
34
35        }
36
37        // 这里模拟VM的实现,返回ThrowableThread自身,大家先认为不是返回NULL
38        public static ThrowableThread getCurrentThread() {
39//            return new ThrowableThread();
40            return null;   // <--- 假设这里在VM的实现里面返回的不是NULL而是当前的ThrowableThread
41        }
42
43        public void start() {
44            run();
45        }
46    }
47
48    private static class ThrowableThreadLocal {
49
50        public ThrowableThreadLocal() {
51
52        }
53
54        public void set(Object value) {
55            ThrowableThread currentThread = ThrowableThread.getCurrentThread();
56            assert null != currentThread;
57            ThrowableThreadLocalMap threadLocalMap = currentThread.threadLocalMap;
58            if (null == threadLocalMap) {
59                threadLocalMap = currentThread.threadLocalMap = new ThrowableThreadLocalMap();
60            }
61            threadLocalMap.put(this, value);
62        }
63
64        public Object get() {
65            ThrowableThread currentThread = ThrowableThread.getCurrentThread();
66            assert null != currentThread;
67            ThrowableThreadLocalMap threadLocalMap = currentThread.threadLocalMap;
68            if (null == threadLocalMap) {
69                return null;
70            }
71            return threadLocalMap.get(this);
72        }
73
74        // 这里其实在ThreadLocal中用的是WeakHashMap
75        public static class ThrowableThreadLocalMap extends HashMap<ThrowableThreadLocal, Object> {
76
77        }
78    }
79}

上面的代码不能运行,只是通过一个自定义的实现说明一下其中的原理和关系。

ThreadLocal和InheritableThreadLocal的局限性

InheritableThreadLocal是ThreadLocal的子类,它们之间的联系是:两者都是线程Thread实例获取ThreadLocal.ThreadLocalMap的一个中间变量。区别是:两者控制ThreadLocal.ThreadLocalMap创建的时机和通过Thread实例获取ThreadLocal.ThreadLocalMap在Thread实例中对应的属性并不一样,导致两者的功能有一点差别。通俗来说两者的功能联系和区别是:

  • ThreadLocal:单个线程生命周期强绑定,只能在某个线程的生命周期内对ThreadLocal进行存取,不能跨线程存取。
1public class ThreadLocalMain {
 2
 3    private static ThreadLocal<String> TL = new ThreadLocal<>();
 4
 5    public static void main(String[] args) throws Exception {
 6        new Thread(() -> {
 7            methodFrame1();
 8        }, "childThread").start();
 9        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
10    }
11
12    private static void methodFrame1() {
13        TL.set("throwable");
14        methodFrame2();
15    }
16
17    private static void methodFrame2() {
18        System.out.println(TL.get());
19    }
20}
21// 输出结果:
22throwable
  • InheritableThreadLocal:(1)可以无感知替代ThreadLocal的功能,当成ThreadLocal使用。(2)明确父-子线程关系的前提下,继承(拷贝)父线程的线程本地变量缓存过的变量,而这个拷贝的时机是子线程Thread实例化时候进行的,也就是子线程实例化完毕后已经完成了InheritableThreadLocal变量的拷贝,这是一个变量传递的过程。
1public class InheritableThreadLocalMain {
 2
 3    // 此处可以尝试替换为ThreadLocal,最后会输出null
 4    static InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>();
 5
 6    public static void main(String[] args) throws Exception {
 7        new Thread(() -> {
 8            // 在父线程中设置变量
 9            ITL.set("throwable");
10            new Thread(() -> {
11                methodFrame1();
12            }, "childThread").start();
13        }, "parentThread").start();
14        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
15    }
16
17    private static void methodFrame1() {
18        methodFrame2();
19    }
20
21    private static void methodFrame2() {
22        System.out.println(ITL.get());
23    }
24}
25// 输出结果:
26throwable

上面提到的两点可以具体参看ThreadLocal、InheritableThreadLocal和Thread三个类的源码,这里笔者把一些必要的注释和源码段贴出:

1// --> java.lang.Thread类的源码片段
 2public class Thread implements Runnable {
 3
 4    // 省略其他代码 
 5
 6    // 这是Thread最基本的构造函数
 7    private Thread(ThreadGroup g, Runnable target, String name,
 8                   long stackSize, AccessControlContext acc,
 9                   boolean inheritThreadLocals) {
 10
 11        // 省略其他代码
 12
 13        Thread parent = currentThread();
 14        this.group = g;
 15        this.daemon = parent.isDaemon();
 16        this.priority = parent.getPriority();
 17        if (security == null || isCCLOverridden(parent.getClass()))
 18            this.contextClassLoader = parent.getContextClassLoader();
 19        else
 20            this.contextClassLoader = parent.contextClassLoader;
 21        this.inheritedAccessControlContext =
 22                acc != null ? acc : AccessController.getContext();
 23        this.target = target;
 24        setPriority(priority);
 25        // inheritThreadLocals一般情况下为true
 26        // 当前子线程实例拷贝父线程的inheritableThreadLocals属性,创建一个新的ThreadLocal.ThreadLocalMap实例赋值到自身的inheritableThreadLocals属性
 27        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
 28            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
 29        this.stackSize = stackSize;
 30        this.tid = nextThreadID();
 31    }
 32
 33    // 省略其他代码
 34}
 35
 36// --> java.lang.ThreadLocal源码片段
 37public class ThreadLocal<T> {
 38
 39    // 省略其他代码 
 40
 41    public void set(T value) {
 42        Thread t = Thread.currentThread();
 43        // 通过当前线程获取线程实例中的threadLocals
 44        ThreadLocalMap map = getMap(t);
 45        // 线程实例中的threadLocals为NULL,实例则创建一个ThreadLocal.ThreadLocalMap实例添加当前ThreadLocal->VALUE到ThreadLocalMap中,如果已经存在ThreadLocalMap则进行覆盖对应的Entry
 46        if (map != null) {
 47            map.set(this, value);
 48        } else {
 49            createMap(t, value);
 50        }
 51    }
 52
 53    // 通过线程实例获取该线程的threadLocals实例,其实是ThreadLocal.ThreadLocalMap类型的属性
 54    ThreadLocalMap getMap(Thread t) {
 55        return t.threadLocals;
 56    }
 57
 58
 59    public T get() {
 60        Thread t = Thread.currentThread();
 61        // 通过当前线程获取线程实例中的threadLocals,再获取ThreadLocal.ThreadLocalMap中匹配上KEY为当前ThreadLocal实例的Entry对应的VALUE
 62        ThreadLocalMap map = getMap(t);
 63        if (map != null) {
 64            ThreadLocalMap.Entry e = map.getEntry(this);
 65            if (e != null) {
 66                @SuppressWarnings("unchecked")
 67                T result = (T)e.value;
 68                return result;
 69            }
 70        }
 71        // 找不到则尝试初始化ThreadLocal.ThreadLocalMap
 72        return setInitialValue();
 73    }
 74
 75    // 如果不存在ThreadLocal.ThreadLocalMap,则通过初始化initialValue()方法的返回值,构造一个ThreadLocal.ThreadLocalMap
 76    private T setInitialValue() {
 77        T value = initialValue();
 78        Thread t = Thread.currentThread();
 79        ThreadLocalMap map = getMap(t);
 80        if (map != null)
 81            map.set(this, value);
 82        else
 83            createMap(t, value);
 84        return value;
 85    }
 86
 87    // 省略其他代码 
 88}
 89
 90// --> java.lang.InheritableThreadLocal源码 - 太简单,全量贴出
 91public class InheritableThreadLocal<T> extends ThreadLocal<T> {
 92
 93    // 这个方法使用在线程Thread的构造函数里面ThreadLocal.createInheritedMap(),基于父线程InheritableThreadLocal的属性创建子线程的InheritableThreadLocal属性,它的返回值决定了拷贝父线程的属性时候传入子线程的值
 94    protected T childValue(T parentValue) {
 95        return parentValue;
 96    }
 97
 98    // 覆盖获取线程实例中的绑定的ThreadLocalMap为Thread#inheritableThreadLocals,这个方法其实是覆盖了ThreadLocal中对应的方法,应该加@Override注解
 99    ThreadLocalMap getMap(Thread t) {
100       return t.inheritableThreadLocals;
101    }
102
103    // 覆盖创建ThreadLocalMap的逻辑,赋值到线程实例中的inheritableThreadLocals,而不是threadLocals,这个方法其实是覆盖了ThreadLocal中对应的方法,应该加@Override注解
104    void createMap(Thread t, T firstValue) {
105        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
106    }
107}

一定要注意,这里的setInitialValue()方法很重要,一个新的线程****Thread实例在初始化(对于InheritableThreadLocal而言继承父线程的线程本地变量)或者是首次调用ThreadLocal#set(),会通过此setInitialValue()方法去构造一个全新的ThreadLocal.ThreadLocalMap,会直接使用createMap()方法。以前面提到的两个例子,贴一个图加深理解:Example-1:

1e294766060c2c98575cd5da8afd9d9f.png

Example-2:

7a71f2b2be9acc3cdc5216a2136f8338.png

ThreadLocal、InheritableThreadLocal的最大局限性就是:无法为预先创建好(未投入使用)的线程实例传递变量(准确来说是首次传递某些场景是可行的,而后面由于线程池中的线程是复用的,无法进行更新或者修改变量的传递值),泛线程池Executor体系、TimerTask和ForkJoinPool等一般会预先创建(核心)线程,也就它们都是无法在线程池中由预创建的子线程执行的Runnable任务实例中使用。例如下面的方式会导致参数传递失败:

1public class InheritableThreadForExecutor {
 2
 3    static final InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>();
 4    static final Executor EXECUTOR = Executors.newFixedThreadPool(1);
 5
 6    public static void main(String[] args) throws Exception {
 7        ITL.set("throwable");
 8        EXECUTOR.execute(() -> {
 9            System.out.println(ITL.get());
10        });
11        ITL.set("doge");
12        EXECUTOR.execute(() -> {
13            System.out.println(ITL.get());
14        });
15        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
16    }
17}
18// 输出结果:
19throwable
20throwable   # <--- 可见此处参数传递出现异常

首次变量传递成功是因为线程池中的所有子线程都是派生自main线程。

TTL的简单使用

TTL的使用方式在它的项目README.md或者项目中的单元测试有十分详细的介绍,先引入依赖com.alibaba:transmittable-thread-local:2.11.4,这里演示一个例子:

1// 父-子线程
 2public class TtlSample1 {
 3
 4    static TransmittableThreadLocal<String> TTL = new TransmittableThreadLocal<>();
 5
 6    public static void main(String[] args) throws Exception {
 7        new Thread(() -> {
 8            // 在父线程中设置变量
 9            TTL.set("throwable");
10            new Thread(TtlRunnable.get(() -> {
11                methodFrame1();
12            }), "childThread").start();
13        }, "parentThread").start();
14        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
15    }
16
17    private static void methodFrame1() {
18        methodFrame2();
19    }
20
21    private static void methodFrame2() {
22        System.out.println(TTL.get());
23    }
24}
25// 输出:
26throwable
27
28// 线程池
29public class TtlSample2 {
30
31    static TransmittableThreadLocal<String> TTL = new TransmittableThreadLocal<>();
32    static final Executor EXECUTOR = Executors.newFixedThreadPool(1);
33
34    public static void main(String[] args) throws Exception {
35        TTL.set("throwable");
36        EXECUTOR.execute(TtlRunnable.get(() -> {
37            System.out.println(TTL.get());
38        }));
39        TTL.set("doge");
40        EXECUTOR.execute(TtlRunnable.get(() -> {
41            System.out.println(TTL.get());
42        }));
43        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
44    }
45}
46// 输出:
47throwable
48doge

TTL实现的基本原理

TTL设计上使用了大量的委托(Delegate),委托是C#里面的说法,对标Java的设计模式就是代理模式。举个简单的例子:

1@Slf4j
 2public class StaticDelegate {
 3
 4    public static void main(String[] args) throws Exception {
 5        new RunnableDelegate(() -> log.info("Hello World!")).run();
 6    }
 7
 8    @Slf4j
 9    @RequiredArgsConstructor
10    private static final class RunnableDelegate implements Runnable {
11
12        private final Runnable runnable;
13
14        @Override
15        public void run() {
16            try {
17                log.info("Before run...");
18                runnable.run();
19                log.info("After run...");
20            } finally {
21                log.info("Finally run...");
22            }
23        }
24    }
25}
26// 输出结果:
2723:45:27.763 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - Before run...
2823:45:27.766 [main] INFO club.throwable.juc.StaticDelegate - Hello World!
2923:45:27.766 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - After run...
3023:45:27.766 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - Finally run...

委托如果使用纯熟的话,可以做出很多十分有用的功能,例如可以基于Micrometer去统计任务的执行时间,上报到Prometheus,然后用Grafana做监控和展示:

1// 需要引入io.micrometer:micrometer-core:${version}
 2@Slf4j
 3public class MeterDelegate {
 4
 5    public static void main(String[] args) throws Exception {
 6        Executor executor = Executors.newFixedThreadPool(1);
 7        Runnable task = () -> {
 8            try {
 9                // 模拟耗时
10                Thread.sleep(1000);
11            } catch (Exception ignore) {
12
13            }
14        };
15        Map<String, String> tags = new HashMap<>(8);
16        tags.put("_class", "MeterDelegate");
17        executor.execute(new MicrometerDelegate(task, "test-task", tags));
18        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
19    }
20
21    @Slf4j
22    @RequiredArgsConstructor
23    private static final class MicrometerDelegate implements Runnable {
24
25        private final Runnable runnable;
26        private final String taskType;
27        private final Map<String, String> tags;
28
29        @Override
30        public void run() {
31            long start = System.currentTimeMillis();
32            try {
33                runnable.run();
34            } finally {
35                long end = System.currentTimeMillis();
36                List<Tag> tagsList = Lists.newArrayList();
37                Optional.ofNullable(tags).ifPresent(x -> x.forEach((k, v) -> {
38                    tagsList.add(Tag.of(k, v));
39                }));
40                Metrics.summary(taskType, tagsList).record(end - start);
41            }
42        }
43    }
44}

委托理论上只要不线程栈溢出,可以无限层级地包装,有点像洋葱的结构,原始的目标方法会被包裹在最里面并且最后执行:

1    public static void main(String[] args) throws Exception {
 2        Runnable target = () -> log.info("target");
 3        Delegate level1 = new Delegate(target);
 4        Delegate level2 = new Delegate(level1);
 5        Delegate level3 = new Delegate(level2);
 6        // ......
 7    }
 8
 9    @RequiredArgsConstructor
10    static class Delegate implements Runnable{
11
12        private final Runnable runnable;
13
14        @Override
15        public void run() {
16            runnable.run();
17        }
18    }

当然,委托的层级越多,代码结构就会越复杂,不利于理解和维护。多层级委托这个洋葱结构,再配合Java反射API剥离对具体方法调用的依赖,就是Java中切面编程的普遍原理,spring-aop就是这样实现的。委托如果再结合Agent和字节码增强(使用ASM、Javassist等),可以实现类加载时期替换对应的Runnable、Callable或者一般接口的实现,这样就能无感知完成了增强功能。此外,TTL中还使用了模板方法模式,如:

1@Slf4j
 2public class TemplateMethod {
 3
 4    public static void main(String[] args) throws Exception {
 5        Runnable runnable = () -> log.info("Hello World!");
 6        Template template = new Template(runnable) {
 7            @Override
 8            protected void beforeExecute() {
 9                log.info("BeforeExecute...");
10            }
11
12            @Override
13            protected void afterExecute() {
14                log.info("AfterExecute...");
15            }
16        };
17        template.run();
18    }
19
20    @RequiredArgsConstructor
21    static abstract class Template implements Runnable {
22
23        private final Runnable runnable;
24
25        protected void beforeExecute() {
26
27        }
28
29        @Override
30        public void run() {
31            beforeExecute();
32            runnable.run();
33            afterExecute();
34        }
35
36        protected void afterExecute() {
37
38        }
39    }
40}
41// 输出结果:
4200:25:32.862 [main] INFO club.throwable.juc.TemplateMethod - BeforeExecute...
4300:25:32.865 [main] INFO club.throwable.juc.TemplateMethod - Hello World!
4400:25:32.865 [main] INFO club.throwable.juc.TemplateMethod - AfterExecute...

分析了两种设计模式,下面简单理解一下TTL实现的伪代码:

1# TTL extends InheritableThreadLocal
2# Holder of TTL -> InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> [? => NULL]
3(1)创建一个全局的Holder,用于保存父线程(或者明确了父线程的子线程)的TTL对象,这里注意,是TTL对象,Holder是当作Set使用
4(2)(父)线程A中使用了TTL,则所有设置的变量会被TTL捕获
5(3)(子)线程B使用了TtlRunnable(Runnable的TTL实现,使用了前面提到的委托,像Callable的实现是TtlCallable),会重放所有存储在TTL中的,来自于线程A的存储变量
6(4)线程B重放完毕后,清理线程B独立产生的ThreadLocal变量,归还变TTL的变量

主要就是这几步,里面的话术有点抽象,后面一节分析源码的时候会详细讲解。

TTL的源码分析

主要分析:

  • 框架的骨架。
  • 核心类TransmittableThreadLocal。
  • 发射器Transmitter。
  • 捕获、重放和复原。
  • Agent模块。

TTL框架骨架

TTL是一个十分精悍的框架,它依赖少量的类实现了比较强大的功能,除了提供给用户使用的API,还提供了基于Agent和字节码增强实现了无感知增强泛线程池对应类的功能,这一点是比较惊艳的。这里先分析编程式的API,再简单分析Agent部分的实现。笔者阅读TTL框架的时间是2020年五一劳动节前后,当前的最新发行版本为2.11.4。TTL的项目结构很简单:

1- transmittable-thread-local
2  - com.alibaba.ttl
3   - spi   SPI接口和一些实现
4   - threadpool   线程池增强,包括ThreadFactory和线程池的Wrapper等
5     - agent   线程池的Agent实现相关
6   最外层的包有一些Wrapper的实现和TTL

先看spi包:

>1- spi
2  TtlAttachments
3  TtlAttachmentsDelegate
4  TtlEnhanced
5  TtlWrapper</pre>

b4835340b7f1ee298c97e79168cc5c24.png

TtlEnhanced是TTL的标识接口(空接口),标识具体的组件被TTL增强:

1public interface TtlEnhanced {
2
3}

通过instanceof关键字就可以判断具体的实现是否TTL增强过的组件。TtlWrapper接口继承自接口TtlEnhanced,用于标记实现类可以解包装获得原始实例:

1public interface TtlWrapper<T> extends TtlEnhanced {
2
3    // 返回解包装实例,实际是就是原始实例
4    @NonNull
5    T unwrap();
6}

TtlAttachments接口也是继承自接口TtlEnhanced,用于为TTL添加K-V结构的附件,TtlAttachmentsDelegate是其实现类,K-V的存储实际上是委托给ConcurrentHashMap:

1public interface TtlAttachments extends TtlEnhanced {
 2
 3    // 添加K-V附件
 4    void setTtlAttachment(@NonNull String key, Object value);
 5
 6    // 通过KEY获取值
 7    <T> T getTtlAttachment(@NonNull String key);
 8
 9    // 标识自动包装的KEY,Agent模式会使用自动包装,这个时候会传入一个附件的K-V,其中KEY就是KEY_IS_AUTO_WRAPPER
10    String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper";
11}
12
13// TtlAttachmentsDelegate
14public class TtlAttachmentsDelegate implements TtlAttachments {
15
16    private final ConcurrentMap<String, Object> attachments = new ConcurrentHashMap<String, Object>();
17
18    @Override
19    public void setTtlAttachment(@NonNull String key, Object value) {
20        attachments.put(key, value);
21    }
22
23    @Override
24    @SuppressWarnings("unchecked")
25    public <T> T getTtlAttachment(@NonNull String key) {
26        return (T) attachments.get(key);
27    }
28}

因为TTL的实现覆盖了泛线程池Executor、ExecutorService、ScheduledExecutorService、ForkJoinPool和TimerTask(在TTL中组件已经标记为过期,推荐使用ScheduledExecutorService),范围比较广,短篇幅无法分析所有的源码,而且它们的实现思路是基本一致的,笔者下文只会挑选Executor的实现路线进行分析。

e9a445b0aa203e34983d7722ab9df299.png

核心类TransmittableThreadLocal

TransmittableThreadLocal是TTL的核心类,TTL框架就是用这个类来命名的。先看它的构造函数和关键属性:

1// 函数式接口,TTL拷贝器
 2@FunctionalInterface
 3public interface TtlCopier<T> {
 4
 5    // 拷贝父属性
 6    T copy(T parentValue);
 7}
 8
 9public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T> {
10
11    // 日志句柄,使用的不是SLF4J的接口,而是java.util.logging的实现
12    private static final Logger logger = Logger.getLogger(TransmittableThreadLocal.class.getName());
13
14    // 是否禁用忽略NULL值的语义
15    private final boolean disableIgnoreNullValueSemantics;
16
17    // 默认是false,也就是不禁用忽略NULL值的语义,也就是忽略NULL值,也就是默认的话,NULL值传入不会覆盖原来已经存在的值
18    public TransmittableThreadLocal() {
19        this(false);
20    }
21
22    // 可以通过手动设置,去覆盖IgnoreNullValue的语义,如果设置为true,则是支持NULL值的设置,设置为true的时候,与ThreadLocal的语义一致
23    public TransmittableThreadLocal(boolean disableIgnoreNullValueSemantics) {
24        this.disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics;
25    }
26
27    // 先忽略其他代码
28}

disableIgnoreNullValueSemantics属性相关可以查看Issue157,下文分析方法的时候也会说明具体的场景。TransmittableThreadLocal继承自InheritableThreadLocal,本质就是ThreadLocal,那它到底怎么样保证变量可以在线程池中的线程传递?接着分析其他所有方法:

1public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T> {
 2
 3    // 拷贝器的拷贝方法实现
 4    public T copy(T parentValue) {
 5        return parentValue;
 6    }
 7
 8    // 模板方法,留给子类实现,在TtlRunnable或者TtlCallable执行前回调
 9    protected void beforeExecute() {
 10    }
 11
 12    // 模板方法,留给子类实现,在TtlRunnable或者TtlCallable执行后回调
 13    protected void afterExecute() {
 14    }
 15
 16    // 获取值,直接从InheritableThreadLocal#get()获取
 17    @Override
 18    public final T get() {
 19        T value = super.get();
 20        // 如果值不为NULL 或者 禁用了忽略空值的语义(也就是和ThreadLocal语义一致),则重新添加TTL实例自身到存储器
 21        if (disableIgnoreNullValueSemantics || null != value) addThisToHolder();
 22        return value;
 23    }
 24
 25    @Override
 26    public final void set(T value) {
 27        // 如果不禁用忽略空值的语义,也就是需要忽略空值,并且设置的入参值为空,则做一次彻底的移除,包括从存储器移除TTL自身实例,TTL(ThrealLocalMap)中也移除对应的值
 28        if (!disableIgnoreNullValueSemantics && null == value) {
 29            // may set null to remove value
 30            remove();
 31        } else {
 32            // TTL(ThrealLocalMap)中设置对应的值
 33            super.set(value);
 34            // 添加TTL实例自身到存储器
 35            addThisToHolder();
 36        }
 37    }
 38
 39    // 从存储器移除TTL自身实例,从TTL(ThrealLocalMap)中移除对应的值
 40    @Override
 41    public final void remove() {
 42        removeThisFromHolder();
 43        super.remove();
 44    }
 45
 46    // 从TTL(ThrealLocalMap)中移除对应的值
 47    private void superRemove() {
 48        super.remove();
 49    }
 50
 51    // 拷贝值,主要是拷贝get()的返回值
 52    private T copyValue() {
 53        return copy(get());
 54    }
 55
 56    // 存储器,本身就是一个InheritableThreadLocal(ThreadLocal)
 57    // 它的存放对象是WeakHashMap<TransmittableThreadLocal<Object>, ?>类型,而WeakHashMap的VALUE总是为NULL,这里当做Set容器使用,WeakHashMap支持NULL值
 58    private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
 59            new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
 60                @Override
 61                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
 62                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
 63                }
 64
 65                @Override
 66                protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
 67                    // 注意这里的WeakHashMap总是拷贝父线程的值
 68                    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
 69                }
 70            };
 71
 72    // 添加TTL自身实例到存储器,不存在则添加策略
 73    @SuppressWarnings("unchecked")
 74    private void addThisToHolder() {
 75        if (!holder.get().containsKey(this)) {
 76            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
 77        }
 78    }
 79
 80    // 从存储器移除TTL自身的实例
 81    private void removeThisFromHolder() {
 82        holder.get().remove(this);
 83    }
 84
 85    // 执行目标方法,isBefore决定回调beforeExecute还是afterExecute,注意此回调方法会吞掉所有的异常只打印日志
 86    private static void doExecuteCallback(boolean isBefore) {
 87        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
 88            try {
 89                if (isBefore) threadLocal.beforeExecute();
 90                else threadLocal.afterExecute();
 91            } catch (Throwable t) {
 92                if (logger.isLoggable(Level.WARNING)) {
 93                    logger.log(Level.WARNING, "TTL exception when " + (isBefore ? "beforeExecute" : "afterExecute") + ", cause: " + t.toString(), t);
 94                }
 95            }
 96        }
 97    }
 98
 99    // DEBUG模式下打印TTL里面的所有值
100    static void dump(@Nullable String title) {
101        if (title != null && title.length() > 0) {
102            System.out.printf("Start TransmittableThreadLocal[%s] Dump...%n", title);
103        } else {
104            System.out.println("Start TransmittableThreadLocal Dump...");
105        }
106
107        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
108            System.out.println(threadLocal.get());
109        }
110        System.out.println("TransmittableThreadLocal Dump end!");
111    }
112
113    // DEBUG模式下打印TTL里面的所有值
114    static void dump() {
115        dump(null);
116    }
117
118    // 省略静态类Transmitter的实现代码
119}

这里一定要记住holder是全局静态的,并且它自身也是一个InheritableThreadLocal(get()方法也是线程隔离的),它实际上就是线程管理所有TransmittableThreadLocal的桥梁。这里可以考虑一个单线程的例子来说明TransmittableThreadLocal的存储架构:

1public class TtlSample3 {
 2
 3    static TransmittableThreadLocal<String> TTL1 = new TransmittableThreadLocal<>();
 4    static TransmittableThreadLocal<String> TTL2 = new TransmittableThreadLocal<>();
 5    static TransmittableThreadLocal<String> TTL3 = new TransmittableThreadLocal<>();
 6
 7    public static void main(String[] args) throws Exception {
 8        TTL1.set("VALUE-1");
 9        TTL2.set("VALUE-2");
10        TTL3.set("VALUE-3");
11    }
12}

这里简化了例子,只演示了单线程的场景,图中的一些对象的哈希码有可能每次启动JVM实例都不一样,这里只是做示例:

03b9971e90b8189559b4fa292e1f8545.png

注释里面也提到,holder里面的WeakHashMap是当成Set容器使用,映射的值都是NULL,每次遍历它的所有KEY就能获取holder里面的所有的TransmittableThreadLocal实例,它是一个全局的存储器,但是本身是一个InheritableThreadLocal,多线程共享后的映射关系会相对复杂:

7f54b107b19c34458b4760f5b4c4a9fa.png

再聊一下disableIgnoreNullValueSemantics的作用,默认情况下disableIgnoreNullValueSemantics=false,TTL如果设置NULL值,会直接从holder移除对应的TTL实例,在TTL#get()方法被调用的时候,如果原来持有的属性不为NULL,该TTL实例会重新加到holder。如果设置disableIgnoreNullValueSemantics=true,则set(null)的语义和ThreadLocal一致。见下面的例子:

1public class TtlSample4 {
 2
 3    static TransmittableThreadLocal<Integer> TL1 = new TransmittableThreadLocal<Integer>(false) {
 4        @Override
 5        protected Integer initialValue() {
 6            return 5;
 7        }
 8
 9        @Override
10        protected Integer childValue(Integer parentValue) {
11            return 10;
12        }
13    };
14
15    static TransmittableThreadLocal<Integer> TL2 = new TransmittableThreadLocal<Integer>(true) {
16        @Override
17        protected Integer initialValue() {
18            return 5;
19        }
20
21        @Override
22        protected Integer childValue(Integer parentValue) {
23            return 10;
24        }
25    };
26
27    public static void main(String[] args) throws Exception {
28        TL1.set(null);
29        TL2.set(null);
30        Thread t1 = new Thread(TtlRunnable.get(() -> {
31            System.out.println(String.format("Thread:%s,value:%s", Thread.currentThread().getName(), TL1.get()));
32        }), "T1");
33
34        Thread t2 = new Thread(TtlRunnable.get(() -> {
35            System.out.println(String.format("Thread:%s,value:%s", Thread.currentThread().getName(), TL2.get()));
36        }), "T2");
37        t1.start();
38        t2.start();
39        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
40    }
41}
42// 输出结果:
43Thread:T2,value:null
44Thread:T1,value:5

这是因为框架的设计者不想把NULL作为有状态的值,如果真的有需要保持和ThreadLocal一致的用法,可以在构造TransmittableThreadLocal实例的时候传入true。

发射器Transmitter

发射器Transmitter是TransmittableThreadLocal的一个公有静态类,它的核心功能是传输所有的TransmittableThreadLocal实例和提供静态方法注册当前线程的变量到其他线程。按照笔者阅读源码的习惯,先看构造函数和关键属性:

1// # TransmittableThreadLocal#Transmitter
 2public static class Transmitter {
 3
 4    // 保存手动注册的ThreadLocal->TtlCopier映射,这里是因为部分API提供了TtlCopier给用户实现
 5    private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>();
 6    // threadLocalHolder更变时候的监视器
 7    private static final Object threadLocalHolderUpdateLock = new Object();
 8    // 标记WeakHashMap中的ThreadLocal的对应值为NULL的属性,便于后面清理
 9    private static final Object threadLocalClearMark = new Object();
10
11    // 默认的拷贝器,影子拷贝,直接返回父值
12    private static final TtlCopier<Object> shadowCopier = new TtlCopier<Object>() {
13        @Override
14        public Object copy(Object parentValue) {
15            return parentValue;
16        }
17    };
18
19    // 私有构造,说明只能通过静态方法提供外部调用
20    private Transmitter() {
21        throw new InstantiationError("Must not instantiate this class");
22    }
23
24    // 私有静态类,快照,保存从holder中捕获的所有TransmittableThreadLocal和外部手动注册保存在threadLocalHolder的ThreadLocal的K-V映射快照
25    private static class Snapshot {
26        final WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
27        final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value;
28
29        private Snapshot(WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
30            this.ttl2Value = ttl2Value;
31            this.threadLocal2Value = threadLocal2Value;
32        }
33    }
34}

Transmitter在设计上是一个典型的工具类,外部只能调用其公有静态方法。接着看其他静态方法:

1// # TransmittableThreadLocal#Transmitter
 2public static class Transmitter {
 3
 4    //######################################### 捕获 ###########################################################
 5
 6    // 捕获当前线程绑定的所有的TransmittableThreadLocal和已经注册的ThreadLocal的值 - 使用了用时拷贝快照的策略
 7    // 笔者注:它一般在构造任务实例的时候被调用,因此当前线程相对于子线程或者线程池的任务就是父线程,其实本质是捕获父线程的所有线程本地变量的值
 8    @NonNull
 9    public static Object capture() {
 10        return new Snapshot(captureTtlValues(), captureThreadLocalValues());
 11    }
 12
 13    // 新建一个WeakHashMap,遍历TransmittableThreadLocal#holder中的所有TransmittableThreadLocal的Entry,获取K-V,存放到这个新的WeakHashMap返回
 14    private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
 15        WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
 16        for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
 17            ttl2Value.put(threadLocal, threadLocal.copyValue());
 18        }
 19        return ttl2Value;
 20    }
 21
 22    // 新建一个WeakHashMap,遍历threadLocalHolder中的所有ThreadLocal的Entry,获取K-V,存放到这个新的WeakHashMap返回
 23    private static WeakHashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
 24        final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value = new WeakHashMap<ThreadLocal<Object>, Object>();
 25        for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
 26            final ThreadLocal<Object> threadLocal = entry.getKey();
 27            final TtlCopier<Object> copier = entry.getValue();
 28            threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
 29        }
 30        return threadLocal2Value;
 31    }
 32
 33    //######################################### 重放 ###########################################################
 34
 35    // 重放capture()方法中捕获的TransmittableThreadLocal和手动注册的ThreadLocal中的值,本质是重新拷贝holder中的所有变量,生成新的快照
 36    // 笔者注:重放操作一般会在子线程或者线程池中的线程的任务执行的时候调用,因此此时的holder#get()拿到的是子线程的原来就存在的本地线程变量,重放操作就是把这些子线程原有的本地线程变量备份
 37    @NonNull
 38    public static Object replay(@NonNull Object captured) {
 39        final Snapshot capturedSnapshot = (Snapshot) captured;
 40        return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
 41    }
 42
 43    // 重放所有的TTL的值
 44    @NonNull
 45    private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) {
 46        // 新建一个新的备份WeakHashMap,其实也是一个快照
 47        WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
 48        // 这里的循环针对的是子线程,用于获取的是子线程的所有线程本地变量
 49        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
 50            TransmittableThreadLocal<Object> threadLocal = iterator.next();
 51
 52            // 拷贝holder当前线程(子线程)绑定的所有TransmittableThreadLocal的K-V结构到备份中
 53            backup.put(threadLocal, threadLocal.get());
 54
 55            // 清理所有的非捕获快照中的TTL变量,以防有中间过程引入的额外的TTL变量(除了父线程的本地变量)影响了任务执行后的重放操作
 56            // 简单来说就是:移除所有子线程的不包含在父线程捕获的线程本地变量集合的中所有子线程本地变量和对应的值
 57            /**
 58             * 这个问题可以举个简单的例子:
 59             * static TransmittableThreadLocal<Integer> TTL = new TransmittableThreadLocal<>();
 60             * 
 61             * 线程池中的子线程C中原来初始化的时候,在线程C中绑定了TTL的值为10087,C线程是核心线程不会主动销毁。
 62             * 
 63             * 父线程P在没有设置TTL值的前提下,调用了线程C去执行任务,那么在C线程的Runnable包装类中通过TTL#get()就会获取到10087,显然是不符合预期的
 64             *
 65             * 所以,在C线程的Runnable包装类之前之前,要从C线程的线程本地变量,移除掉不包含在父线程P中的所有线程本地变量,确保Runnable包装类执行期间只能拿到父线程中捕获到的线程本地变量
 66             *
 67             * 下面这个判断和移除做的就是这个工作
 68             */
 69            if (!captured.containsKey(threadLocal)) {
 70                iterator.remove();
 71                threadLocal.superRemove();
 72            }
 73        }
 74
 75        // 重新设置TTL的值到捕获的快照中
 76        // 其实真实的意图是:把从父线程中捕获的所有线程本地变量重写设置到TTL中,本质上,子线程holder里面的TTL绑定的值会被刷新
 77        setTtlValuesTo(captured);
 78
 79        // 回调模板方法beforeExecute
 80        doExecuteCallback(true);
 81
 82        return backup;
 83    }
 84
 85    // 提取WeakHashMap中的KeySet,遍历所有的TransmittableThreadLocal,重新设置VALUE
 86    private static void setTtlValuesTo(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
 87        for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
 88            TransmittableThreadLocal<Object> threadLocal = entry.getKey();
 89            // 重新设置TTL值,本质上,当前线程(子线程)holder里面的TTL绑定的值会被刷新
 90            threadLocal.set(entry.getValue());
 91        }
 92    }
 93
 94    // 重放所有的手动注册的ThreadLocal的值
 95    private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> captured) {
 96        // 新建备份
 97        final WeakHashMap<ThreadLocal<Object>, Object> backup = new WeakHashMap<ThreadLocal<Object>, Object>();
 98        // 注意这里是遍历捕获的快照中的ThreadLocal
 99        for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
100            final ThreadLocal<Object> threadLocal = entry.getKey();
101            // 添加到备份中
102            backup.put(threadLocal, threadLocal.get());
103            final Object value = entry.getValue();
104            // 如果值为清除标记则绑定在当前线程的变量进行remove,否则设置值覆盖
105            if (value == threadLocalClearMark) threadLocal.remove();
106            else threadLocal.set(value);
107        }
108        return backup;
109    }
110
111    // 从relay()或者clear()方法中恢复TransmittableThreadLocal和手工注册的ThreadLocal的值对应的备份
112    // 笔者注:恢复操作一般会在子线程或者线程池中的线程的任务执行的时候调用
113    public static void restore(@NonNull Object backup) {
114        final Snapshot backupSnapshot = (Snapshot) backup;
115        restoreTtlValues(backupSnapshot.ttl2Value);
116        restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
117    }
118
119    private static void restoreTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> backup) {
120        // 回调模板方法afterExecute
121        doExecuteCallback(false);
122        // 这里的循环针对的是子线程,用于获取的是子线程的所有线程本地变量
123        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
124            TransmittableThreadLocal<Object> threadLocal = iterator.next();
125            // 如果子线程原来就绑定的线程本地变量的值,如果不包含某个父线程传来的对象,那么就删除
126            // 这一步可以结合前面reply操作里面的方法段一起思考,如果不删除的话,就相当于子线程的原来存在的线程本地变量绑定值被父线程对应的值污染了
127            if (!backup.containsKey(threadLocal)) {
128                iterator.remove();
129                threadLocal.superRemove();
130            }
131        }
132
133        // 重新设置TTL的值到捕获的快照中
134        // 其实真实的意图是:把子线程的线程本地变量恢复到reply()的备份(前面的循环已经做了父线程捕获变量的判断),本质上,等于把holder中绑定于子线程本地变量的部分恢复到reply操作之前的状态
135        setTtlValuesTo(backup);
136    }
137
138    // 恢复所有的手动注册的ThreadLocal的值
139    private static void restoreThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> backup) {
140        for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
141            final ThreadLocal<Object> threadLocal = entry.getKey();
142            threadLocal.set(entry.getValue());
143        }
144    }
145}

这里三个核心方法,看起来比较抽象,要结合多线程的场景和一些空间想象进行推敲才能比较容易地理解:

  • capture():捕获操作,父线程原来就存在的线程本地变量映射和手动注册的线程本地变量映射捕获,得到捕获的快照值captured。
  • reply():重放操作,子线程原来就存在的线程本地变量映射和手动注册的线程本地变量生成备份backup,刷新captured的所有值到子线程在全局存储器holder中绑定的值。
  • restore():复原操作,子线程原来就存在的线程本地变量映射和手动注册的线程本地变量恢复成backup。

setTtlValuesTo()这个方法比较隐蔽,要特别要结合多线程和空间思维去思考,例如当入参是captured,本质是从父线程捕获到的绑定在父线程的所有线程本地变量,调用的时机在reply()和restore(),这两个方法只会在子线程中调用,setTtlValuesTo()里面拿到的TransmittableThreadLocal实例调用set()方法相当于把绑定在父线程的所有线程本地变量的值全部刷新到子线程当前绑定的TTL中的线程本地变量的值,更深层次地想,是基于外部的传入值刷新了子线程绑定在全局存储器holder里面绑定到该子线程的线程本地变量的值。

cb41eea6230360670649007f28e7baea.png

Transmitter还有不少静态工具方法,这里不做展开,可以参考项目里面的测试demo和README.md进行调试。

捕获、重放和复原

其实上面一节已经介绍了Transmitter提供的捕获、重放和复原的API,这一节主要结合分析TtlRunnable中的相关逻辑。TtlRunnable的源码如下:

1public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
 2
 3    // 存放从父线程捕获得到的线程本地变量映射的备份
 4    private final AtomicReference<Object> capturedRef;
 5    // 原始的Runable实例
 6    private final Runnable runnable;
 7    // 执行之后是否释放TTL值引用
 8    private final boolean releaseTtlValueReferenceAfterRun;
 9
10    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
11        // 这里关键点:TtlRunnable实例化的时候就已经进行了线程本地变量的捕获,所以一定是针对父线程的,因为此时任务还没提交到线程池
12        this.capturedRef = new AtomicReference<Object>(capture());
13        this.runnable = runnable;
14        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
15    }
16
17    @Override
18    public void run() {
19        // 获取父线程捕获到的线程本地变量映射的备份,做一些前置判断
20        Object captured = capturedRef.get();
21        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
22            throw new IllegalStateException("TTL value reference is released after run!");
23        }
24        // 重放操作
25        Object backup = replay(captured);
26        try {
27            // 真正的Runnable调用
28            runnable.run();
29        } finally {
30            // 复原操作
31            restore(backup);
32        }
33    }
34
35    @Nullable
36    public static TtlRunnable get(@Nullable Runnable runnable) {
37        return get(runnable, false, false);
38    }
39
40    @Nullable
41    public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
42        if (null == runnable) return null;
43        if (runnable instanceof TtlEnhanced) {
44            // avoid redundant decoration, and ensure idempotency
45            if (idempotent) return (TtlRunnable) runnable;
46            else throw new IllegalStateException("Already TtlRunnable!");
47        }
48        return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
49    }
50
51    // 省略其他不太重要的方法
52}

其实关注点只需要放在构造函数、run()方法,其他都是基于此做修饰或者扩展。构造函数的源码说明,capture()在TtlRunnable实例化的时候已经被调用,实例化它的一般就是父线程,所以整体的执行流程如下:

c88047326c075ce975a90d05c1ab412c.png

Agent模块

启用Agent功能,需要在Java的启动参数添加:-javaagent:path/to/transmittable-thread-local-x.yzx.jar。原理是通过Instrumentation回调激发ClassFileTransformer实现目标类的字节码增强,使用到javassist,被增强的类主要是泛线程池的类:

  • Executor体系:主要包括ThreadPoolExecutor和ScheduledThreadPoolExecutor,对应的字节码增强类实现是TtlExecutorTransformlet。
  • ForkJoinPool:对应的字节码增强类实现是TtlForkJoinTransformlet。
  • TimerTask:对应的字节码增强类实现是TtlTimerTaskTransformlet。

Agent的入口类是TtlAgent,这里查看对应的源码:

1public final class TtlAgent {
 2
 3    public static void premain(String agentArgs, @NonNull Instrumentation inst) {
 4        kvs = splitCommaColonStringToKV(agentArgs);
 5
 6        Logger.setLoggerImplType(getLogImplTypeFromAgentArgs(kvs));
 7        final Logger logger = Logger.getLogger(TtlAgent.class);
 8
 9        try {
10            logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst);
11            final boolean disableInheritableForThreadPool = isDisableInheritableForThreadPool();
12            // 装载所有的JavassistTransformlet
13            final List<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>();
14            transformletList.add(new TtlExecutorTransformlet(disableInheritableForThreadPool));
15            transformletList.add(new TtlForkJoinTransformlet(disableInheritableForThreadPool));
16            if (isEnableTimerTask()) transformletList.add(new TtlTimerTaskTransformlet());
17            final ClassFileTransformer transformer = new TtlTransformer(transformletList);
18            inst.addTransformer(transformer, true);
19            logger.info("[TtlAgent.premain] addTransformer " + transformer.getClass() + " success");
20            logger.info("[TtlAgent.premain] end");
21            ttlAgentLoaded = true;
22        } catch (Exception e) {
23            String msg = "Fail to load TtlAgent , cause: " + e.toString();
24            logger.log(Level.SEVERE, msg, e);
25            throw new IllegalStateException(msg, e);
26        }
27    }
28}

List<JavassistTransformlet>作为参数传入ClassFileTransformer的实现类TtlTransformer中,其中的转换方法为:

1public class TtlTransformer implements ClassFileTransformer {
 2
 3    private final List<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>();
 4
 5    TtlTransformer(List<? extends JavassistTransformlet> transformletList) {
 6        for (JavassistTransformlet transformlet : transformletList) {
 7            this.transformletList.add(transformlet);
 8            logger.info("[TtlTransformer] add Transformlet " + transformlet.getClass() + " success");
 9        }
10    }
11
12    @Override
13    public final byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class<?> classBeingRedefined,
14                                  final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) {
15        try {
16            // Lambda has no class file, no need to transform, just return.
17            if (classFile == null) return NO_TRANSFORM;
18            final String className = toClassName(classFile);
19            ClassInfo classInfo = new ClassInfo(className, classFileBuffer, loader);
20            // 这里做变量,如果字节码被修改,则跳出循环返回
21            for (JavassistTransformlet transformlet : transformletList) {
22                transformlet.doTransform(classInfo);
23                if (classInfo.isModified()) return classInfo.getCtClass().toBytecode();
24            }
25        } catch (Throwable t) {
26            String msg = "Fail to transform class " + classFile + ", cause: " + t.toString();
27            logger.log(Level.SEVERE, msg, t);
28            throw new IllegalStateException(msg, t);
29        }
30        return NO_TRANSFORM;
31    }
32}

这里挑选TtlExecutorTransformlet的部分方法来看:

1    @Override
 2    public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException {
 3        // 如果当前加载的类包含java.util.concurrent.ThreadPoolExecutor或者java.util.concurrent.ScheduledThreadPoolExecutor
 4        if (EXECUTOR_CLASS_NAMES.contains(classInfo.getClassName())) {
 5            final CtClass clazz = classInfo.getCtClass();
 6            // 遍历所有的方法进行增强
 7            for (CtMethod method : clazz.getDeclaredMethods()) {
 8                updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method);
 9            }
10            // 省略其他代码
11        } 
12        // 省略其他代码
13    }
14
15    private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(@NonNull final CtMethod method) throws NotFoundException, CannotCompileException {
16        final int modifiers = method.getModifiers();
17        if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) return;
18        // 这里主要在java.lang.Runnable构造时候调用com.alibaba.ttl.TtlRunnable#get()包装为com.alibaba.ttl.TtlRunnable
19        // 在java.util.concurrent.Callable构造时候调用com.alibaba.ttl.TtlCallable#get()包装为com.alibaba.ttl.TtlCallable
20        // 并且设置附件K-V为ttl.is.auto.wrapper=true
21        CtClass[] parameterTypes = method.getParameterTypes();
22        StringBuilder insertCode = new StringBuilder();
23        for (int i = 0; i < parameterTypes.length; i++) {
24            final String paramTypeName = parameterTypes[i].getName();
25            if (PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.containsKey(paramTypeName)) {
26                String code = String.format(
27                        // decorate to TTL wrapper,
28                        // and then set AutoWrapper attachment/Tag
29                        "$%d = %s.get($%d, false, true);"
30                                + "ncom.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.setAutoWrapperAttachment($%<d);",
31                        i + 1, PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.get(paramTypeName), i + 1);
32                logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ": " + code);
33                insertCode.append(code);
34            }
35        }
36        if (insertCode.length() > 0) method.insertBefore(insertCode.toString());
37    }

上面分析的方法的功能,就是让java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor的字节码被增强,提交的java.lang.Runnable类型的任务会被包装为TtlRunnable,提交的java.util.concurrent.Callable类型的任务会被包装为TtlCallable,实现了无入侵无感知地嵌入TTL的功能。

小结

TTL在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。它是一个Java标准库,为框架/中间件设施开发提供的标配能力,项目代码精悍,只依赖了javassist做字节码增强,实现Agent模式下的近乎无入侵提供TTL功能的特性。TTL能在业务代码中实现透明/自动完成所有异步执行上下文的可定制、规范化的捕捉/传递,如果恰好碰到异步执行时上下文传递的问题,建议可以尝试此库。

线程池上下文参数传递 传统做法是在任务执行前设置ThreadLocal变量,但线程池中的任务可能由不同的线程执行,导致主线程设置的ThreadLocal变量在任务线程中不可见。任务可能在另一个线程执行,该线程的ThreadLocal变量未被设置,导致任务无法获取到主线程的上下文。如果这个线程之前执行过某个任务,并且该任务设置了ThreadLocal变量,那么这些变量可能仍然存在于该线程中,导致后续任务访问到旧的数据,或者如果任务没有正确清理ThreadLocal变量,可能导致内存泄漏或数据混乱。 阅读详情

相关推荐

QtNodeEditor实战:构建高效可视化编程环境的完整指南

本文是QtNodeEditor实战的完整指南,详细介绍了如何利用这一基于Qt框架的可视化编程工具构建高效节点编辑器。内容涵盖从快速集成、自定义节点模型到深入理解MVC数据流架构,并提供了样式定制、场景序列化、动态端口等高级功能的实现方法,帮助开发者快速搭建灵活、可维护的可视化编程环境。

lemon的博客 697

Java多线程:向线程传递参数的三种方法

然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。在这个过程中最先要做的就是向线程中传递数据。​​​​​​在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。..

Java后端技术栈 4559

【NLG】(六)文本生成评价指标—— ROUGE原理及代码示例

前奏: 【NLG】(一)文本生成评价指标——BLEU原理及代码示例 【NLG】(二)文本生成评价指标—— METEOR原理及代码示例 【NLG】(三)文本生成评价指标—— ENTROPY原理及代码示例 【NLG】(四)文本生成评价指标—— diversity原理及代码示例 【NLG】(五)文本生成评价指标—— kl_divergence原理及代码示例 1.ROUGE原理 2.优缺点 3.如何算ROUGE ...

凝眸伏笔的博客 1万+

如何在线程间传递参数

前言 最近排查业务系统一个与跨线程共享数据(或者说传递数据)错误的问题,决定在这里梳理一下线程间数据传输的问题。跨线程传输数据的方式有很多,后续再写文章介绍,本次只对ThreadLocal及其衍生的类InheritableThreadLocal、TransmittableThreadLocal做一些使用的梳理与原理的分析。 应用场景 1.业务系统参数传递 假如你有一个web系统,每次访问客户端会有一些公共参数传递进来,然后你的不同的业务方法,都想使用,这个时候要么你做一层防腐层,将所有有用的参数封装一下接连

暴雪的博客 3799

RK3588 MIPI屏幕调试避坑指南:双通道8Lane下DSC压缩屏的花屏、分层问题排查实录

本文详细解析了RK3588平台在双通道8Lane MIPI接口下调试DSC压缩屏时遇到的花屏、分层问题。通过硬件信号完整性检查、软件配置验证及帧数据分析,提供了一套系统化的排查方法,并深入探讨了初始化序列优化和双通道同步机制,帮助工程师高效解决高分辨率屏幕显示异常问题。

weixin_27015733的博客 279

2025年最新自动化/控制保研夏令营&预推免面试真题分享(东南大学苏州校区/华东理工/南航/天大)

笔者来2021级本科自动化专业,以下部分将介绍我在夏令营以及预推免期间发生经历和问题。

Control Everything 2577

线程池传递对象参数_Java高级多线程

简述:认识多线程,先了解 程序(program)、进程(process)、线程(thread)。程序(program):还未执行的静态代码进程(process):程序的一次执行过程,或正在运行的一个程序,并且有生命周期线程(thread):进程可以细化为线程,程序内部的一条执行路径,如果一个进程同一时间并行多个线程,就是支持多线程。Ps:一个进程中的多个线程可以共享堆和方法区,但是栈和程...

weixin_39517520的博客 1117

可控异常的契约化设计:在 ABAP 中用 CX_STATIC_CHECK 把可预期错误变成接口的一部分

文章摘要: 本文探讨了ABAP系统中异常处理的工程化设计原则,强调将可预期的业务异常作为接口契约的一部分。核心观点是:对于调用方能够合理处理的错误情况,应使用继承自CX_STATIC_CHECK的受检异常,通过方法签名强制调用方处理或显式转发。文章对比了ABAP三大异常根类的适用场景,提出"三问法"判断异常是否适合受检处理,并给出了包含上下文信息的异常类实现示例。通过Fiori文件上传案例,展示了如何在分层架构中合理传递和转换异常,最终在OData服务层将业务异常映射为标准错误响应。这种

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3038

Java基础——多线程

目录什么是多线程进程与线程并行与并发实现多线程继承Thread实现Runnable接口实现Callable接⼝线程池Thread类线程优先级守护线程线程的生命周期sleep方法yield方法join方法interrupt方法线程同步synchronized锁Lock锁线程通信死锁 什么是多线程 进程与线程 并行与并发 实现多线程 继承Thread 实现Runnable接口 实现Callable接⼝...

mazouri的博客 1244

【调优】OpenClaw调优之模型(tokens)篇

OpenClaw用户常遇四大痛点:规则被无视、API账单暴涨、低效模型选择、记忆丢失。通过/context list命令可解析上下文组成,包括工作区配置、引导参数限制、系统提示词等关键信息。重点需关注Token消耗来源,如核心配置文件、技能工具说明等,避免静默截断。建议精简非必要内容,优化记忆机制,平衡功能与成本。该分析帮助用户减少无效操作,合理控制API支出。

海兰 981

手把手教你学Simulink--基于新能源与储能系统的场景实例:压缩空气储能(CAES)系统热力学建模

本文介绍了使用Simulink进行压缩空气储能(CAES)系统热力学建模的详细步骤。系统模型包含压缩机、储气装置、膨胀机、热交换器和控制器等核心模块,通过MATLAB函数计算各部件参数,并设置仿真条件。文章详细说明了从模型创建到性能评估的完整流程,包括压缩/膨胀过程的能量转换、温度压力变化等关键指标分析。该建模方法可验证设计方案的有效性,并为系统优化提供参考,适用于新能源与储能领域的研究与应用。

MHD0815的博客 9139

全链路跟踪关键技术-ThreadLocal

应用场景的文章 Java 多线程上下文传递在复杂场景下的实践 by vivo互联网技术(海外商城租户区分) 2021-02-01 Spring Security OAuth2.0认证授权五:用户信息扩展到jwt2021-01-14 再谈Token认证,如何快速方便获取用户信息 By 尹吉欢2019-06-11 Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失 By 尹吉欢 2017-12-13 上篇http://cxytiandi.com/blog/det...

maoyeqiu的专栏 605

从0到1:OpenClaw 助力单元测试代码自动化与覆盖率飞升

OpenClaw 的横空出世,宛如一道曙光,照亮了这片黑暗的领域。它作为一款强大的本地 AI 助手,拥有着令人惊叹的能力。OpenClaw 能够深入理解代码的逻辑结构,自动分析代码中的各种功能和潜在的风险点,从而精准地生成高质量的单元测试代码。它就像是一位不知疲倦的测试专家,能够快速、准确地完成那些繁琐的测试编写工作,将开发者从重复劳动中解放出来,让他们能够将更多的精力投入到更具创造性和价值的工作中。

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 468

ThreadLocal引发的思考

ThreadLocal引发的思考 事情起因 项目在做降级方案时,引入了hystrix,对一些不重要的功能依赖进行降级熔断操作。引入hystrix后,则引发一个ThreadLocal的问题。被@HystrixCommand注解的方法中,获取当前用户信息时,出现空指针。 原理解析 开始分析原因…… 1) 看看hystrix的原理,发现hystrix是基于线程池或者信号量2种方式来调用被注解的方法,hystrix默认采用线程池方式 2) 项目中当前用户信息是ThreadLocal变量 3) 那就可以理解了,Thr

代码让AI扣 561

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

java 线程池使用_面试必备:Java线程池解析

前言掌握线程池是后端程序员的基本要求,相信大家求职面试过程中,几乎都会被问到有关于线程池的问题。我在网上搜集了几道经典的线程池面试题,并以此为切入点,谈谈我对线程池的理解。如果有哪里理解不正确,非常希望大家指出,接下来大家一起分析学习吧。github 地址github.com/whx123/Java…经典面试题面试问题1:Java的线程池说一下,各个参数的作用,如何进行的?面试问题2:按线程池内部...

weixin_39662955的博客 276

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

参数可动态调节的线程池实现

背景 线程池是一种基于池化思想管理线程的工具,使用线程池可以减少创建销毁线程的开销,避免线程过多导致系统资源耗尽。在高并发的任务处理场景,线程池的使用是必不可少的。随着线程池的使用,逐渐发现一个问题,线程池的参数如何设置? 线程池参数中有三个比较关键的参数,分别是corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、workQueueSzie(工作队列大小)。根据任务的类型可以区分为IO密集型和CPU密集型,对于CPU密集型,一般经验是设置corePoolSize=CPU核数

周忠太的博客 1104

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

线程池传参问题

改用线程池代码,单个参数import time import threadpool def sayhello(str): print "Hello ",str time.sleep(2) name_list =['xiaozi','aa','bb','cc'] start_time = time.time() pool = threadpool.ThreadPool(10) ...

qq_33962513的博客 2213

传递参数到线程的n个方法

本片文章的议题是有关于传递参数到线程的几种方法。 首先我们要知道什么是线程,什么时候要用到线程,如何去使用线程,如何更好的利用线程来完成工作。 线程是程序可执行片段的最小单元,是组成运行时程序的基本单元,一个进程有至少一个线程组成。一般在并行处理等待事件的时候要用到线程,如等待网络响应,等待I/O通讯,后台事务处理等情况。使用线程其实很简单,在.net框架下面你首先要定义一个函数来完成一些...

weixin_33958585的博客 256

深入理解 ThreadLocal 参数及其在线程池中的传递机制

是 Java 提供的一个工具类,用于在多线程环境下为每个线程维护独立的变量副本。的变量在线程间是隔离的,每个线程只能访问到自己线程中的副本,其他线程无法访问或修改。用户会话信息存储数据库事务上下文管理日志追踪中的请求 ID是 Java 中一个非常有用的工具类,可以为每个线程维护独立的数据副本,广泛应用于用户会话、事务管理、日志追踪等场景。然而,在线程池中使用时,由于线程复用的特性,会导致数据残留和污染问题。因此,在使用线程池时必须谨慎管理的生命周期,确保任务完成后正确清理。通过使用。

lssffy的博客 3007

线程池传递多个参数和单线程多任务

线程池传递多个参数 import time from multiprocessing.dummy import Pool import requests # 生成偏函数,可用于固定函数的参数 from functools import partial urls = [ 'http://localhost:5436/index', 'http://localhost:5436/deamon', 'http://localhost:5436/result', 'http://

程序烂人的博客 650

Java线程池七个参数详解

java多线程开发时,常常用到线程池技术,这篇文章是对创建java线程池时的七个参数的详细解释。 从源码中可以看出,线程池的构造函数有7个参数,分别是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadF...

IT小跟班 30万+

你都理解创建线程池的参数吗?

多线程可以说是面试官最喜欢拿来问的题目之一了,可谓是老生之常谈,不管你是新手还是老司机,我相信你一定会在面试过程中遇到过有关多线程的一些问题。那我现在就充当一次面试官,我...

后端进阶 1283

线程池的解析以及7个参数

文章目录1.线程池的七个参数2.线程池的执行流程3.线程池的属性标识3.1、 核心属性3.2、线程池状态变化4.线程池的execute()方法执行4.1、execute()4.2、addWorker() 1.线程池的七个参数 1、corePoolSize: 核心线程数 2、maximumPoolSize: 最大线程数 3、keepAliveTime: 最大空闲时间 4、TimeUnit unit: 时间单位 5、BlockingQueue workQueue: 阻塞队列 6、ThreadFactory th

好好玩的博客 5919

牛!线程池(详解):三大方法、四种拒绝策略、七大参数

线程池(重点) 一:线程池:三大方法,七大参数,四种拒绝策略   池化技术:   01:程序的运行,本质 :占用系统的资源! 优化资源的使用! =>池化技术   02:线程池、连接池、内存池、对象池///......创建、销毁。 十分浪费资源   03:池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。   线程池的好处:   01:降低资源的消耗   02:提高响应的速度   03:方便管理   (重点)线程复用、可以控制最大并发数、管理...

weixin_46784898的博客 376

ThreadLocal原理总结

一:ThreadLocal是什么? ThreadLocal,即线程本地变量。一个共享变量存进该容器相当于在线程内部拷贝了一个副本。ThreadLocal里面的变量都是存在当前线程的。当操作ThreadLocal里面的变量时,实际操作的是存在自己线程的那个变量副本,该变量副本对于每一个线程都是独立的,从而实现了变量的隔离性,保证了线程安全。 二:ThreadLocal保证线程变量隔离性的原理 每一个Thread线程都会拥有自己的一个成员变量ThreadLocalMap,该变量默认为空(实际是Thre

m0_57713282的博客 1万+

面试必备:ThreadLocal详解

无论是,我们都会跟打交道,今天就跟大家聊聊哈~,即线程本地变量。如果你创建了一个变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到的作用,避免了并发场景下的线程安全问题。并发场景下,会存在多个线程同时修改一个共享变量的场景。这就可能会出现。为了解决线性安全问题,可以用加锁的方式,比如使用或者Lock。但是加锁的方式,可能会导致系统变慢。加锁示意图如下:还有另外一种方案,就是使用空间换时间的方式,即使用。使用。...

zhaohuodian的博客 1740
上一篇: python3.7.2怎么安装不了image_redhat安装python3.7
下一篇: mysql 导出数据字典_快捷高效获取数据库的数据字典的方法
weixin_39878745
博客等级 码龄9年 23粉丝 133原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值