从TL、ITL到TTL详解

这篇具有很好参考价值的文章主要介绍了从TL、ITL到TTL详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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);    }}@Overridepublic final void set(T value) {    if (!disableIgnoreNullValueSemantics && null == value) {        remove();    } else {        super.set(value);        //当主线程赋值时,会将自己的TTL放到自己的map中        addThisToHolder();    }}@Overridepublic 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.Transmitterpublic 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:

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

@Overridepublic 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进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略文章来源地址https://www.toymoban.com/news/detail-669461.html

到了这里,关于从TL、ITL到TTL详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RabbitMQ详解(五):过期时间TTL、死信队列、磁盘监控

    过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。 目前有两种方法可以设置 第一种方法是通过队列属性设置,队列中所有消息都有相同的TTL。 第二种方法是对消息进行单独设置,每条消息TTL可以不同。 当同时

    2024年02月04日
    浏览(42)
  • Java利用POI-TL模板导出Word文档

    本文详细介绍了如何使用POI-TL,一个基于Apache POI的Word模板引擎,来导出Word文档。文章内容包括POI-TL的基本介绍,为什么选择POI-TL,以及如何使用POI-TL来处理文本、图片、表格、列表、嵌套、条件判断显示、非空集合循环和图表等内容。

    2024年02月09日
    浏览(106)
  • 【Java】poi-tl 使用Word模板渲染动态表格

    poi-tl 是一个基于 Apache POI 和 FreeMarker 的 Java 模板引擎,可以用于动态生成 Word、Excel、PowerPoint 等文档。使用 poi-tl 可以方便地将数据填充到模板中,生成符合要求的文档。 poi-tl官方文档地址:http://deepoove.com/poi-tl/#_%E7%89%88%E6%9C%AC 先附上导出效果图: 这步建议手动创建,之前客服

    2024年01月21日
    浏览(62)
  • Java使用poi-tl生成word模板-动态数据列表

    Java使用poi-tl生成word模板-动态数据列表 第一步,添加poi-tl的依赖 第二步,准备word模板文件             模板中文本信息可以用    {{  }}   标签指定             例如在word文档中写入:{{var}} //会将word文档中的对位置的标签替换为map中的数据             模板

    2024年02月14日
    浏览(49)
  • TP-Link TL-WR886N路由器设置无线桥接方法详解

    大家都知道,如果家中面积比较大,一台路由器无线信号无法完美覆盖大面积房间,这个时候就可以借助2台无线路由器无线桥接设置,实现无线信号更好的覆盖。今天本文主要介绍下新TP-Link TL-WR886N路由器无线桥接设置教程,感兴趣的朋友不要错过了。 TP-Link TL-WR886N路由器无

    2024年02月05日
    浏览(42)
  • 手机,蓝牙开发板,TTL/USB模块,电脑四者之间的通讯

    通过手机蓝牙连接WeMosD1R32开发板,开发板又通过TTL转USB与电脑连接.手机通过蓝牙控制开发板上的LED灯的开,关,闪等动作,在电脑上打开串口监视工具观察其状态.也可以通过电脑上的串口监视工具来控制开发板上LED灯的动作,而在手机蓝牙监测工具中显示灯的状态. 手机,TTL转USB转

    2024年02月03日
    浏览(36)
  • java 使用POI-TL根据word模版,生成word文件,含图片,富文本。

    1.引入mavna坐标` 2 .poi-tl-ext插件主要用于富文本内容格式在word展现 3.word模版创建 3.具体代码实现 4.本文的miniourl路径实质为网络路径的文件。

    2024年02月16日
    浏览(62)
  • Java使用poi-tl1.9.1生成Word文档的几个小技巧

    目录 前言 一、poi-tl简介 1、什么是poi-tl 2、常见的word生成对比 3、poi-tl功能点 二、poi-tl文档生成 1、模板准备 2、目标参数填充 3、生成效果  三、可能会遇到的问题 1、混合图表生成报错 2、图表参数设置技巧  总结         也许在您的工作当中会碰到如下的一些场景,比

    2024年02月16日
    浏览(50)
  • java使用poi-tl导出word及转换PDF后的合并导出pdf

    为某单位开发的一款项目申报审批系统,用户需求在申报阶段填写的信息资料能够导出PDF。且项目申报的报告正文为用户上传,所以需要合并导出。 在项目初期阶段使用的是PDF的预设模板导出,因为以前使用过,比较熟悉。所以优先选择此方法,但项目测试阶段发现问题,因

    2024年02月06日
    浏览(80)
  • 详解信号逻辑电平标准: CMOS、TTL、LVCMOS、LVTTL、ECL、PECL、LVPECL、LVDS、CML

    Refer: (285条消息) FPGA几种电平:TTL,CMOS以及LVTTL,LVCMOS_Love coldplay的博客-CSDN博客 常用的电平标准有TTL、CMOS、LVTTL、LVCMOS、ECL、PECL、LVPECL、RS232、RS485等,还有一些速度比较高的LVDS、GTL、PGTL、CML、HSTL、SSTL等。 供电电源、电平标准以及使用注意事项 Vcc:5V;VOH=2.4V;VOL 因为2.

    2024年02月09日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包