【工作技术栈】【源码解读】一次好奇心:单例模式下的ThreadLocal为什么要使用volatile和synchronized?

news/2025/3/19 4:31:28/

目录

  • 前言
  • 现象
  • 分析原因
  • 思考感悟

前言

最近写业务有一个AOP的切面使用了threadlocal方式存储了业务执行时需要交给AOP处理的数据。但是我发现Aspect使用了一个名字叫ThreadClient的类获取了一个threadlocal的引用,通常我的threadlocal都是定义到Aspect内只给当前业务使用并且是已经初始化过的静态常量(这样在类被初始化(加载字节码->链接->初始化)时,静态常量就会被赋值),所以没有见过还有单例模式提供threadlocal的,所以就看了一下怎么实现的。

现象

版本(抛开版本就是耍流氓~)
jdk8
sprintboot 2.3.12

现象
这个不是问题,我们直接看下代码

public class ThreadLocalClient {static volatile ThreadLocal<Map> threadLocal = null;// 类级别变量(用作锁,其实可以直接用ThreadLocalClient.class)private final static String LOCK = "LOCK";public static ThreadLocal<Map> getThreadLocal() {if (threadLocal == null) {synchronized (LOCK){ if (threadLocal == null) {threadLocal = new ThreadLocal<Map>();}	}}return threadLocal;}

分析原因

首先有几个疑问直接在我心里出现:
1、threadlocal不是线程私有变量么?为什么需要单例?
2、threadlocal如果是静态常量,线程之间的数据不会串线吧?
3、threadlocal为什么使用volatile修饰?修饰了保持了内存可见性之后,为什么还要synchronized关键字修饰赋值的临界区?

OK,接下来开始一个个解答:
问题1和问题2:

    /*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/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();}........................./*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

首先看一段源码,就是threadlocal.get()方法,这里面可以发现不论是任何线程,首先取得是当前执行的线程引用Thread t,Thread.currentThread();可以在任何地方执行,似乎跟当前类没有关系,唯一有关系的就是ThreadLocalMap.Entry e = map.getEntry(this);
以当前ThreadLocal引用作为key获取到当前线程t中的ThreadLocalMap成员变量的一个Entry,那么到这里我们就可以回答问题1和问题2了,首先单例模式或者使用静态变量都可以直接用来设置ThreadLocal,并且线程之间不会串线,因为每一个线程都是独立的ThreadLocalMap,虽然传入的key(threadlocal)相同。

问题3:
首先复习一下volatile关键字,这个关键字最详细的我觉得在周志明老师的《深入理解jvm虚拟机》的章节中解释的比较清晰,首先这个关键字有两个特性:第一个就是同步变量的可见性,第二个就是代码指令的重排序。具体解释这里不详细赘述了,有一个重要的结论,volatile关键字从计算机原理的角度来看,无法真的保证线程的可见性,因为线程栈是私有的,特别是在对共享变量进行修改后写入的操作时(如果只是读取就还好),当线程1要修改值时就必须要将操作数压入栈中,修改完成后再写入内存,而在读取的到栈的过程中就会出现并发的情况,但两个线程确实保持了内存的可见性,只是写内存的时候可能会互相覆盖,因此必须配合synchronized关键字来做线程同步达到强同步的作用。
理解上面的解释后,threadlocal变量的初始化就非常好解释了,首先threadlocal必须要被初始化,并且不能出现线程1初始化后线程2又覆盖了原来的单例,也就是说thread只能初始化一次!为什么呢?问题一和问题二的解答中说过,当前线程的引用作为key来获取线程变量,如果被另一个线程赋值为其他引用的话,那么先前的线程变量就会丢失!因此synchronized和volatile一起配合才能保证threadlocal只会初始化一次。

思考感悟

其实我觉得最安全的办法就是使用静态常量直接赋值的方式,在类刚加载时,该变量就已经被赋值,并且使用final修饰之后,引用更加安全。


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

相关文章

电脑磁盘分区形式是什么?如何更改?

磁盘分区形式介绍 在了解为什么以及如何更改分区形式之前&#xff0c;让我们对磁盘分区形式有一个基本的了解。一般来说&#xff0c;分区形式是指主引导记录&#xff08;MBR&#xff09;和 GUID 分区表&#xff08;GPT&#xff09;。 MBR和GPT是Windows系统中常用…

【STM32教程】第四章 STM32的外部中断EXTI

案例代码及相关资料下载链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1hsIibEmsB91xFclJd-YTYA?pwdjauj 提取码&#xff1a;jauj 1 中断系统 1.1 中断的概念 中断系统的定义&#xff1a;中断是指在主程序运行过程中&#xff0c;出现了特定的中断触发条件…

topscoding主题库模板题

目录 模板题 【模板题】分因数&#xff08;P1101&#xff09; 【模板题】区间素数 III&#xff08;P1113&#xff09; 进制转换 III (任意转任意) &#xff08;P2463&#xff09; AB Problem&#xff08;高精度加法&#xff09; A-B Problem&#xff08;高精度减法&…

Spring MVC:请求转发与请求重定向

Spring MVC 请求转发请求重定向附 请求转发 转发&#xff08; forward &#xff09;&#xff0c;指服务器接收请求后&#xff0c;从一个资源跳转到另一个资源中。请求转发是一次请求&#xff0c;不会改变浏览器的请求地址。 简单示例&#xff1a; 1.通过 String 类型的返回值…

Debian下Hadoop集群安装

Debian下Hadoop集群安装 依赖安装 jdk 8 sudo apt-get update && sudo apt-get install -y wget apt-transport-https wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo tee /etc/apt/keyrings/adoptium.asc echo "deb [signed…

青大数据机构【2013】

关键字&#xff1a; 邻接表空间复杂度、求无向图连通分量&#xff08;BFS、DFS&#xff09;、B树根节点最小关键字、平均查找长度最小的排序、二叉树排序叶子结点次序不变、不同次序建立二叉排序树及中序遍历、直接插入排序特点、强连通分量、邻接矩阵邻接表 一、单选&#x…

cmd 90 validate error!(达梦数据库日志报错)

达梦数据库报错 error-cmd 90 validate error! 环境介绍1 解决办法 环境介绍 某生产环境数据库启动后&#xff0c;dm_实例名_202309.log&#xff0c;偶尔报错cmd 90 validate error! 1 解决办法 接口用错了&#xff0c;消息非法&#xff0c;比如用 6 的 JDBC 连 7 或 7 的 …

HTTP RESTFul RPC

一、简介 &#xff08;1&#xff09;HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种应用层协议。它经常用于在Web和服务器之间通讯&#xff0c;或服务与服务之间通讯。 &#xff08;2&#xff09;RESTFul 约束HTTP协议实现上的规范设计。 &#xff08;3&am…