ThreadLocal及阿里(TransmittableThreadLocal,TTL)分析

TTL类关系图
ThreadLocal <- InheritableThreadLocal <- TransmittableThreadLocal

在这里插入图片描述

1. ThreadLocal

ThreadLocal 类提供线程本地(局部)变量。每个线程都有自己独立初始化的变量副本。
TheadLocal 允许我们存储仅由特定线程访问的数据,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。

1.1 使用场景

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

1.2 ThreadLocal主要方法

// get() 获取线程本地变量,如果没有,则调用setInitialValue() 初始化并设置
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}
// 初始化
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}
// 设置线程本地变量
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
// 移除线程本地变量
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}

1.3 实现原理

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

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
}

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

public class Thread implements Runnable {/*** 与此线程相关的ThreadLocal值。此Map由 ThreadLocal 类维护。*/ThreadLocal.ThreadLocalMap threadLocals = null;/*** 与此线程相关的 InheritableThreadLocal 值。此映射由 InheritableThreadLocal 类维护*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

1.4 缺陷

ThreadLocal在创建子线程时不会把线程本地变量拷贝到子线程中,这就会导致子线程无法获取到本地线程保存的线程信息

2. InheritableThreadLocal

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

2.1 使用场景

  • 新建子线程需要集成父线程中的线程本地变量时,可以使用 InheritableThreadLocal 实现
/*** 可继承ThreadLocal,子线程将继承父线程中ITL变量value*/
private static final ThreadLocal<Integer> BIZ_ITL = new InheritableThreadLocal();/*** 运行结果:* main-thread-get:123* sub-thread-get:123*/
public static void main(String[] args) {BIZ_ITL.set(123);System.out.println("main-thread-get:"+BIZ_ITL.get());new Thread(()->{System.out.println("sub-thread-get:"+BIZ_ITL.get());}).start();
}

2.2 InheritableThreadLocal 主要方法

  • getMap() createMap() 使用 ITLMap ,非 TLMap
ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

2.3 实现原理

main线程调用get方法跳转到InheritableThreadLocal重写的createMap,让inheritableThreadLocals赋初值;后续http请求会copy main线程中的数据,同样赋予inheritableThreadLocals 值。
子程继承父线程 线程本地变量值 是在Thread创建时,copy父类现成中的 ITLMap中的key和Value:
Thread构造函数:调用init方法,在init方法中子线程根据父线程的ITLMap创建自己的ITLMap(传递、继承)。

// 初始化Thread
public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {// 获取父线程Thread parent = currentThread();// 省略其他步骤if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

2.4 缺陷

使用线程池时,线程不会每次都创建,也就不会进行父子线程之间的数据传递;如果使用池化的线程,就不会进行父子线程本地变量的拷贝。

3. TransmittableThreadLocal

3.1 使用场景

ThreadLocal和InheritableThreadLocal 能够完成变量的线程本地化和父子线程中的value传递。
但是现实项目中大多数线程池化在线程池中,因此,提交任务的线程无法将 提交现成的本地变量传递给执行task的任务线程。
TTL组件功能:在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
业务期望:上下文生命周期的操作从业务逻辑中分离出来。业务逻辑不涉及生命周期,就不会有业务代码如疏忽清理而引发的问题了。

3.2 TransmittableThreadLocal主要方法

整个上下文的传递流程或说生命周期可以规范化成:
捕捉、回放和恢复这3个操作,即CRR(capture/replay/restore)模式。
CRR :

  1. capture方法:抓取线程(线程A)的所有TTL值。
  2. replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值,并返回回放前TTL值的备份
  3. restore方法:恢复线程B执行replay方法之前的TTL值(即备份)
// 实现代码示例 TtlRunnable.class
// 初始化TtlRunnable对象
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {// TransmittableThreadLocal.Transmitter.capture() 抓取当前线程中的TTL值的备份this.capturedRef = new AtomicReference<>(capture());this.runnable = runnable;this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}@Override
public void run() {// 取出TTL值备份数据final Object captured = capturedRef.get();if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {throw new IllegalStateException("TTL value reference is released after run!");}// 回放在capture方法中抓取的TTL值并返回回放前TTL值的备份final Object backup = replay(captured);try {runnable.run();} finally {// 恢复线程B执行replay方法之前的TTL值(即备份)restore(backup);}
}

3.3 使用样例

private static final int DEFAULT_SIZE = 8;
private static final TransmittableThreadLocal<Map<String, Object>> CACHE = new TransmittableThreadLocal<Map<String, Object>>() {@Overrideprotected Map<String, Object> initialValue() {return new HashMap<>(DEFAULT_SIZE);}
};Executor ttlExecutor = TtlExecutors.getTtlExecutor(new ThreadPoolExecutor(properties.getCoreSize(),properties.getMaxSize(),properties.getKeepAliveSeconds(),TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadPoolExecutor.CallerRunsPolicy()
));ttlExecutor.execute(TtlRunnable.get(() -> {})); 

参考资料
阿里TTL(TransmittableThreadLocal)分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/464558.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux nohup命令和

参考资料 linux后台运行nohup命令的使用及2>&1字符详解 目录 前期准备一. 基本语法二. 执行时不指定日志文件三. 执行后不想要日志文件四. nohup命令的执行与kill4.1 执行4.2 kill 前期准备 &#x1f4c4;handle_file.sh #!/bin/bashecho "文件复制开始..."…

Flink从入门到实践(二):Flink DataStream API

文章目录 系列文章索引三、DataStream API1、官网2、获取执行环境&#xff08;Environment&#xff09;3、数据接入&#xff08;Source&#xff09;&#xff08;1&#xff09;总览&#xff08;2&#xff09;代码实例&#xff08;1.18版本已过时的&#xff09;&#xff08;3&…

罗德里格斯公式简要介绍

一、罗德里格斯公式&#xff08;Rodrigues’ rotation formula&#xff09;是一个用于计算绕任意轴旋转向量的数学公式。它是由欧仁罗德里格斯&#xff08;Olinde Rodrigues&#xff09;在1840年提出的。这个公式在三维空间中描述了一个向量绕着单位向量旋转θ角度后的结果。 …

C++对象继承

继承概念&#xff1a; 首先引入一个生活例子&#xff0c;普通人是一个类对象&#xff0c;学生是一个类对象&#xff0c;普通人拥有的属性学生一定会有&#xff0c;学生拥有的属性普通人不一定有。类比一下&#xff0c;把普通人抽象为A对象&#xff0c;学生抽象为B对象&#xf…

Easy Excel动态表头的实现

步骤&#xff1a; 1.查找官方API文档理解实现 2.实现融入到代码里面 一&#xff1a;Easy Excel动态头实时生成头写入 动态头实时生成头写入 二&#xff1a;实现 目的&#xff1a;实现表头为&#xff0c;第一列是固定列&#xff0c;第二列为动态生成的时间段的每一天的日期…

2024年【上海市安全员C3证】考试及上海市安全员C3证新版试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【上海市安全员C3证】考试及上海市安全员C3证新版试题&#xff0c;包含上海市安全员C3证考试答案和解析及上海市安全员C3证新版试题练习。安全生产模拟考试一点通结合国家上海市安全员C3证考试最新大纲及上海市…

3D立方体图册

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>3D立方体图册</title><style>* {pad…

在 Windows上恢复删除照片的 4 种有效方法

您是否曾在 Windows 7/8/10/11 中不小心删除过照片&#xff1f;如何轻松快速地恢复已删除的照片&#xff1f;在这里这篇文章列出了几种在Windows 11/10/8/7中恢复已删除照片的可行方法&#xff0c;而MiniTool数据恢复软件 是丢失照片恢复的最佳选择。 意外删除的照片 根据一项…

Scrum敏捷开发管理全流程-敏捷管理工具

Leangoo领歌是款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速创新。…

力扣 第 383 场周赛 解题报告 | KMP

力扣 第 383 场周赛 解题报告 | KMP 链接 前言 一个人能走的多远不在于他在顺境时能走的多快&#xff0c;而在于他在逆境时多久能找到曾经的自己。 T1 修改矩阵 思路&#xff1a;模拟 时间复杂度&#xff1a; O ( m n ) O(mn) O(mn) class Solution:def modifiedMatrix(se…

【数学建模】【2024年】【第40届】【MCM/ICM】【E题 财产保险的可持续性】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem E: Sustainability of Property Insurance Extreme-weather events are becoming a crisis for property owners and insurers. The world has endured “more than $1 trillion in damages from more than …

LeetCode Python - 6.Z字形变换

文章目录 题目答案运行结果 题目 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你的输…