ThreadLocal InheritableThreadLocal TransmittableThreadLocal的使用以及原理

news/2024/12/27 2:10:10/

ThreadLocal

每个线程向ThreadLocal设置值,再取值,实现线程之间的隔离

public class ThreadLocalCase1 {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Random random = new Random(); for (int i = 0; i < 5; i++) {Thread thread = new Thread(() -> {int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "开始执行,放入值,值为 : " + value);System.out.println(Thread.currentThread().getName() + "结束执行,进行取值,值为 : " + threadLocal.get());}); thread.setName("thread-" + i); thread.start();}}
}

结果

thread-0开始执行,放入值,值为 : 7406
thread-4开始执行,放入值,值为 : 5258
thread-3开始执行,放入值,值为 : 9672
thread-2开始执行,放入值,值为 : 8583
thread-1开始执行,放入值,值为 : 9311
thread-2结束执行,进行取值,值为 : 8583
thread-3结束执行,进行取值,值为 : 9672
thread-4结束执行,进行取值,值为 : 5258
thread-0结束执行,进行取值,值为 : 7406
thread-1结束执行,进行取值,值为 : 9311

实现了线程之间的隔离性

主线程向ThreadLocal设置值,每个子线程再取值

public class ThreadLocalCase2 {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});thread.setName("thread-1");thread.start();System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}
}

结果

main放入值,值为 : 3831
main进行取值,值为 : 3831
thread-1进行取值,值为 : null

发现主线程向ThreadLocal设置值,每个子线程再取值时为null,这时需要换用InheritableThreadLocal

InheritableThreadLocal

主线程向InheritableThreadLocal设置值,子线程再取值

public class ThreadLocalCase2 {private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});thread.setName("thread-1");thread.start();System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}
}

结果

main放入值,值为 : 2046
main进行取值,值为 : 2046
thread-1进行取值,值为 : 2046
public class ThreadLocalCase3 {private static ExecutorService executor = Executors.newFixedThreadPool(2);private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {for (int i = 0; i < 5; i++) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);executor.execute(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());threadLocal.remove();}}
}

结果

main放入值,值为 : 8525
main进行取值,值为 : 8525
main放入值,值为 : 7802
main进行取值,值为 : 7802
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 3570
pool-1-thread-2进行取值,值为 : 7802
main进行取值,值为 : 3570
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 5081
main进行取值,值为 : 5081
pool-1-thread-2进行取值,值为 : 7802
main放入值,值为 : 4829
main进行取值,值为 : 4829
pool-1-thread-1进行取值,值为 : 8525

结果发现线程池取出了两次7802和三次8525,主线程中设置的3570、5081、4829在线程池中没有被取出,发生了错误。
这是由于InheritableThreadLocal会保证子线程能读取父线程中的数据,但线程池中的核心线程是复用的,所以有可能会发生重复读取的情况。

TransmittableThreadLocal

开启循环,每个循环中主线程使用InheritableThreadLocal进行设置,使用线程池来进行取值,解决线程复用产生的问题

public class ThreadLocalCase4 {private static ExecutorService executor = Executors.newFixedThreadPool(2);private static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();public static void main(String[] args) {for (int i = 0; i < 5; i++) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);executor.execute(TtlRunnable.get(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}));System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());threadLocal.remove();}}
}

结果

main放入值,值为 : 974
main进行取值,值为 : 974
main放入值,值为 : 4545
main进行取值,值为 : 4545
main放入值,值为 : 5901
pool-1-thread-1进行取值,值为 : 974
main进行取值,值为 : 5901
pool-1-thread-1进行取值,值为 : 5901
pool-1-thread-2进行取值,值为 : 4545
main放入值,值为 : 3716
main进行取值,值为 : 3716
pool-1-thread-1进行取值,值为 : 3716
main放入值,值为 : 2452
main进行取值,值为 : 2452
pool-1-thread-2进行取值,值为 : 2452

结果是即使线程池的线程被复用,读取的结果也是正常的

ThreadLocal原理

  • Thread
public class Thread implements Runnable {//省略..../* * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal*/ThreadLocal.ThreadLocalMap threadLocals = null;//省略....
}
  • ThreadLocal类:
    • ThreadLocal是一个泛型类,用于存储每个线程的本地变量副本。
      每个线程通过ThreadLocal实例获取和操作自己的变量副本,避免了多线程间的资源竞争。
  • ThreadLocalMap:
    • ThreadLocalMapThreadLocal的内部静态类,用于存储线程局部变量。
      每个线程都有一个独立的ThreadLocalMap实例,用来存储该线程的ThreadLocal变量。
      ThreadLocalMapThreadLocal实例作为键,实际存储的值作为值。
  • get()和set()方法:
    • 使用ThreadLocal的get()方法,可以获取当前线程的ThreadLocal变量。
      使用ThreadLocal的set()方法,可以设置当前线程的ThreadLocal变量。
      get()和set()方法内部会调用Thread.currentThread()获取当前线程,然后在该线程的ThreadLocalMap中查找或设置对应的值。
      在这里插入图片描述
      内存泄露问题
  • ThreadLocalMap中的键使用弱引用:
    • ThreadLocalMap中的键是对ThreadLocal实例的弱引用。当没有强引用指向ThreadLocal实例时,垃圾回收器会回收这个ThreadLocal实例,导致ThreadLocalMap中的键变为null。
    • jdk1.8环境下的ThreadLocal采取嗅探机制,将调用get或set方法时,会主动探测是否含有key为空的value没有被回收的情况,如果有会主动清理。但我们依旧要在使用完后主动的调用remove
      在这里插入图片描述

InheritableThreadLocal原理

InheritableThreadLocal重写的方法

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);}
}

Thread结构

public class Thread implements Runnable {//省略..../* * 存储本线程自身的ThreadLocal*/ThreadLocal.ThreadLocalMap threadLocals = null;/** 从父线程集成而来的ThreadLocalMap,*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//省略....
}

主线程中调用InheritableThreadLocal的set方法
set依旧是ThreadLocal

public void set(T value) {Thread t = Thread.currentThread();//被InheritableThreadLocal重写,第一次为空ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {//被InheritableThreadLocal重写,创建ThreadLocalMap赋值给inheritableThreadLocals变量createMap(t, value);}
}

这时就是Thread中的inheritableThreadLocals变量存储ThreadLocalMap

子线程初始化

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {//省略....//inheritThreadLocals为true//parent.inheritableThreadLocals就是在主线程进行set的时候生成为所以不为nullif (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//省略....        }

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)就是将主线程的ThreadLocalMap拷贝到子线程中

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}
/*** 将父线程的ThreadLocalMap拷贝到此线程中*/
private ThreadLocalMap(ThreadLocalMap parentMap) {//父线程的ThreadLocalMap的entry数组 Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);// 这里的table就是此线程中的ThreadLocalMap的entry数组 table = new Entry[len];// 循环进行拷贝 parentMap 的记录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) {//这里被InheritableThreadLocal重写,直接返回value,不做任何操作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++;}}}
}

到这里就将父线程中的值复制到子线程中了
在这里插入图片描述


http://www.ppmy.cn/news/47448.html

相关文章

JavaScript的基础语法学习

文章目录 一、JavaScript let 和 const二、JavaScript JSON三、javascript:void(0) 含义四、JavaScript 异步编程总结 一、JavaScript let 和 const let 声明的变量只在 let 命令所在的代码块内有效。 const 声明一个只读的常量&#xff0c;一旦声明&#xff0c;常量的值就不…

APIs -- DOM正则表达式

1. 介绍 正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在JavaScript中&#xff0c;正则表达式也是对象通常用来查找、替换那些符合正则表达式的文本&#xff0c;许多语言都支持正则表达式。正则表达式在JavaScript中的使用场景: 例如验证表单:用户名表单…

MyBatis --- 缓存、逆向工程、分页插件

一、MyBatis的缓存 1.1、MyBatis的一级缓存 一级缓存是SqlSession级别的&#xff0c;通过同一个SqlSession查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就会从缓存中直接获取&#xff0c;不会从数据库重新访问 使一级缓存失效的四种情况&#xff1a; 1、…

ElasticSearch——详解主从模式,以及主节点的选取算法(一)

详解主从模式&#xff0c;以及主节点的选取算法 Discovery模块负责发现集群中的节点&#xff0c;以及选择主节点。 ES支持多种不同Discovery类型选择&#xff0c;内置的实现称为Zen Discovery&#xff0c;其他的包括公有云平台亚马逊的EC2、谷歌的GCE等。本文讨论内置的Zen Di…

rviz 可视化手机 IMU

原博客&#xff1a;https://www.cnblogs.com/hitcm/p/5616364.html 原代码&#xff1a;https://github.com/hitcm/Android_Camera-IMU.git 上面说的不太详细&#xff0c;出现了无法可视化 IMU 转交的情况。git 的 issue 中也有人遇到这个问题。本博客记录了自己如何克服 BUG 并…

【C++】map和set的模拟实现

文章目录 1、map、set和红黑树源码的截取2、红黑树的迭代器3、代码部分3-1、Set.h3-2、Map.h3-3、RBTee.h3-4、测试代码 1、map、set和红黑树源码的截取 我们红黑树的节点只需要用到value值就够了&#xff0c;value是什么&#xff0c;节点就存什么。但是&#xff0c;红黑树的源…

MySQL学习笔记第一天

第02章 MySQL环境搭建 1.MySQL的卸载 步骤1&#xff1a;停止MySQL服务 在卸载之前&#xff0c;先停止MySQL8.0的服务。按键盘上的“Ctrl Alt Delete”组合键&#xff0c;打开“任务管理器”对话框&#xff0c;可以在“服务”列表找到“MySQL8.0”的服务&#xff0c;如果现…

肖 sir_就业课__014python讲解

python讲解 一、python梳理 1、python 数据类型有哪些&#xff1f; 字符、列表、元组、字典、集合 2、列表、元组、字典、集合的区别&#xff1f; 3、python中函数&#xff1f; &#xff08;1&#xff09;自定义函数 def 函数名&#xff08;&#xff09; &#xff08;2&#…