从TL、ITL到TT

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

1、概述

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

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

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

2、ThreadLocal

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

2.1、初始化——initialValule

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

2.1.1、重写initialValue

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

2.1.2、调用ThreadLocal.withInitial

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

他会创建一个SuppliedThreadLocal内部类

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

该类重写了initialValue方法

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        //当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化
        return supplier.get();
    }
}

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

2.2、赋值——set

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

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

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

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

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

2.3、取值——get

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

2.4、清空——remove

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

3、InheritableThreadLocal

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

我们先来看一个例子

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

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

子线程ThreadLocal:A

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

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

3.2、ITL的解决方案

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

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

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

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

//父线程部分:
public void set(T value) {
    Thread t = Thread.currentThread();
    //该方法被ITL重写,访问inheritableThreadLocals为null
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    //该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals
        createMap(t, value);
}

//子线程部分:
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    //省略一些代码...

    //获取当前线程(父线程、也就是创建子线程的线程)
    Thread parent = currentThread();
    //1、允许ThreadLocal遗传(这个默认为true)
    //2、inheritableThreadLocals不为空,因为父线程调用set了
    //父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

//createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    //深拷贝父线程ThreadLocalMap
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //childValue被ITL重写,返回父线程ThreadLocal的值
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

使用ITL的效果

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

        thread.join();
}

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

子线程ThreadLocal:B

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

4.、TransmittableThreadLocal

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

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

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

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

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

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

4.2、TTL的解决方案

4.2.1、TTL的使用

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

引入依赖

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

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

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

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

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

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

4.2.2、TTL对ThreadLocal的封装

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

//TransmittableThreadLocal.addThisToHolder()
private void addThisToHolder() {
    //InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
    if (!holder.get().containsKey(this)) {
        //holder是静态变量,他会把TTL存到当前线程的map中
        //value是null,他其实是把Map当Set用
        //主线程赋值时,会获取主线程的holderMap,然后把TTL存进去
        holder.get().put((TransmittableThreadLocal<Object>) this, null);
    }
}

@Override
public final void set(T value) {
    if (!disableIgnoreNullValueSemantics && null == value) {
        remove();
    } else {
        super.set(value);
        //当主线程赋值时,会将自己的TTL放到自己的map中
        addThisToHolder();
    }
}

@Override
public final T get() {
    T value = super.get();
    if (disableIgnoreNullValueSemantics || null != value) 
        addThisToHolder();
    return value;
}

4.2.3、TTL对任务的封装

//我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
    if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
        return executorService;
    }
    //入参是线程池,通过包装类代理线程池的操作
    return new ExecutorServiceTtlWrapper(executorService);
}

//ExecutorServiceTtlWrapper.submit()
public Future<?> submit(@NonNull Runnable task) {
    //将提交的任务进行封装
    return executorService.submit(TtlRunnable.get(task));
}

4.2.3.1、任务构建

TtlRunnable构造方法

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

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

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

//TrasmitableThreadLocal.Transmitter
public static Object capture() {
    //获取ttl的值构建快照
    return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}

private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
    HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
    for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
        //将主线程TTL的值存到当前任务中
        ttl2Value.put(threadLocal, threadLocal.copyValue());
    }
    return ttl2Value;
}

4.2.3.2、任务执行

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

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

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

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

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

TTL官方性能测试

作者:京东物流 刘朝永

来源:京东云开发者 自猿其说文章来源地址https://www.toymoban.com/news/detail-573691.html

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

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

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

相关文章

  • SpringMVC- ThreadLocal变量的注意点

    在Web应用中,尤其是在使用Spring框架或类似的服务器端Java技术时, ThreadLocal 是一种常用的方式来存储每个请求的用户信息或上下文数据。然而,由于Web服务器通常使用线程池来处理请求,因此理解和正确使用 ThreadLocal 变得至关重要。 线程池和ThreadLocal 在线程池中,线程是被

    2024年01月21日
    浏览(32)
  • 线程安全—ThreadLocal

    定义: ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为每个线程都创建了一个副本,存的是自己,那么每个线程可以访问自己内部的副本变量。 ThreadLocal是用空间换取时间,synchronized是用时间换空间。

    2024年02月11日
    浏览(35)
  • ThreadLocal线程安全示例及其原理

    提示:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的 ThreadLocal是用空间换取时间,synchronized关键

    2024年02月07日
    浏览(44)
  • 【Java】线程数据共享和安全 -ThreadLocal

     🎄欢迎来到@边境矢梦°的csdn博文🎄  🎄本文主要梳理线程数据共享和安全 -ThreadLocal🎄 🌈我是边境矢梦°,一个正在为秋招和算法竞赛做准备的学生🌈 🎆喜欢的朋友可以关注一下 🫰🫰🫰 ,下次更新不迷路🎆 Ps: 月亮越亮说明知识点越重要 (重要性或者难度越大)🌑🌒

    2024年02月09日
    浏览(43)
  • 全局变量、局部变量和静态变量

    定义:在函数外定义的变量是全局变量,全局变量可以为本文件中其它函数所共用,它的有效范围从定义变量的位置开始到本源文件结束。 设全局变量的作用:增加了函数间数据联系的渠道; 不必要时不要使用全局变量,因为:a) 全局变量在程序的全部执行过程中都占用存

    2024年02月06日
    浏览(36)
  • ThreadLocal加切面实现线程级别的方法缓存

    当一个请求线程多次请求A方法时,只会触发一次A方法的实际调用,会将方法结果缓存起来,避免多次调用。 1. 需要一个注解ThreadLocalCache,在需要缓存的方法上加上该注解 2. 需要一个切面,借助ThreadLocal,将结果缓存起来,利用环绕通知来实现方法拦截从缓存中返回方法执行结果 3.1、

    2024年04月10日
    浏览(40)
  • 【C语言趣味教学】(6) 作用域:局部变量 | 全局变量 | 局部变量优先原则 | 利用大括号限制作用域 | 变量的生命周期

        🔗 【C语言趣味教程】专栏介绍👈 猛戳了解!!! 0x00 引入:什么是作用域? 变量和常量在程序中都是有作用范围的,这个范围我们称之为变量的  作用域 (scope) 。 作用域也称为 \\\"定义域\\\",是程序中 定义的变量所存在的区域,出了该区域变量就不能被访问。 变量的作

    2024年02月14日
    浏览(33)
  • 【C语言趣味教程】(6) 作用域:局部变量 | 全局变量 | 局部变量优先原则 | 利用大括号限制作用域 | 变量的生命周期

        🔗 【C语言趣味教程】专栏介绍👈 猛戳了解!!! 0x00 引入:什么是作用域? 变量和常量在程序中都是有作用范围的,这个范围我们称之为变量的  作用域 (scope) 。 作用域也称为 \\\"定义域\\\",是程序中 定义的变量所存在的区域,出了该区域变量就不能被访问。 变量的作

    2024年02月14日
    浏览(51)
  • 涨知识-关于局部内部类访问局部变量

    局部内部类访问局部变量必须用final修饰 局部内部类在访问它所在方法中的局部变量必须用final修饰,为什么? 因为当调用这个方法时,局部变量如果没有final修饰,它的生命周期和方法的生命周期是一样的, 当方法弹栈时,这个局部变量也会消失。 那么如果局部内部类对象

    2024年02月03日
    浏览(44)
  • Java成员变量(全局变量)和局部变量

    在Java中变量类型有 成员变量 、 局部变量 和 静态变量(类变量、static变量) ,本篇文章主要介绍成员变量(全局变量)和局部变量的区别。 根据变量的作用范围,将程序中的变量分为成员变量(全局变量)和局部变量。 一、成员变量(全局变量) 成员变量(全局变量):

    2024年02月04日
    浏览(42)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包