我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么。其实JDK已经为我们提供了ThreadLocal这个东西。
◆
ThreadLocal基本使用
◆
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的主要方法有这么几个:
1 2 3 4 |
initialValue 初始化 set 赋值 get 取值 remove 清空 |
下面来看一个简单的使用代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class ThreadLocalDemo { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { @Override public Integer initialValue() { return 0; } }; static class ThreadDemo implements Runnable { @Override public void run() { for (int i = 0; i < 1000; i++) { threadLocal.set(threadLocal.get() + 1); } System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get()); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(new ThreadDemo()).start(); } } } |
上方代码使用了10个线程循环对一个threadLocal的值进行一千次的加法,如果我们不知道ThreadLocal的原理的话我们可能会觉得最后打印的值一定是1000、2000、3000。。10000或者是线程不安全的值。
但是如果你执行这段代码你会发现最后打印的都是1000。
◆
ThreadLocal原理剖析
◆
现在我们来看一下ThreadLocal是如何实现为每个线程单独维护一个变量的呢。
先来看一下初始化方法。
1 2 3 |
protected T initialValue() { return null; } |
initialValue 默认是返回空的,所以为了避免空指针问题重写了这个方法设置了默认返回值为0,但是呢,虽然这个方法好像是设置默认值的,但是还没有生效,具体请接着往下看。
1 2 3 4 5 6 7 8 |
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } |
我们可以看到set方法首先会获取当前线程,然后通过一个getMap方法获取了ThreadLocalMap,接着来看一下这个map是怎么来的呢。
1 2 3 |
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } |
这个map是在Thread类维护的一个map,下方是Thread类维护的此变量。默认这个map是空的。
1
|
ThreadLocal.ThreadLocalMap threadLocals = null;
|
接着往下看代码,如果获取的时候map不为空,则通过set方法把Thread类的threadLocals变量更新。如果是第一次创建的时候则初始化Thread的threadLocals变量。
下方是createMap的代码:
1 2 3 |
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } |
接下来看个get方法就比较容易理解了。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } return setInitialValue(); } |
注意关注最后的一个return,看到调用的这个方法名我们就可以发现这个ThreadLocal的初始化原来是当第一调用get方法时如果还没有被set的时候才会去获取initialValue 方法的返回值。
1 2 3 4 5 6 7 8 9 10 |
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } |
◆
使用ThreadLocal最应该注意的事项
◆
首先来看一下线程退出的办法:
1 2 3 4 5 6 7 8 9 10 11 12 |
private void exit() { if (group != null) { group.threadTerminated(this); group = null; } target = null; threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; } |
我们看到当线程结束的时候上方第7行会把ThreadLocal的值制为空,这个东西本身是没问题的。但是,如果你是使用的线程池,这个问题可就大了!!!
要知道线程池里的线程执行完一个任务之后紧接着下一个,这中间线程可不会结束,下一个任务获得Thread的值可是上一个任务的遗留数据。
下面是这个问题的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { @Override public Integer initialValue() { return 0; } }; static class ThreadDemo implements Runnable { @Override public void run() { for (int i = 0; i < 1000; i++) { threadLocal.set(threadLocal.get() + 1); } System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get()); //threadLocal.remove(); } } public static void main(String[] args) { ExecutorService executorService= Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executorService.submit(new Thread(new ThreadDemo())); } } |
执行这段代码你就会发现同样的操作在线程池里已经得不到一样的结果了。想要解决这种问题也很简单,只需要把ThreadLocal的值在线程执行完清空就可以了。把第14行注释的代码放开再执行以下你就明白了。
◆
InheritableThreadLocal
◆
其实ThreadLocal还有一个比较强大的子类InheritableThreadLocal,它呢可以把父线程生成的变量传递给子线程。
下面来看一下代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class InheritableThreadLocalDemo { private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<Integer>(); static class ThreadDemo implements Runnable { @Override public void run() { for (int i = 0; i < 1000; i++) { inheritableThreadLocal.set(inheritableThreadLocal.get() + 1); } System.out.println("thread :" + Thread.currentThread().getId() + " is" + inheritableThreadLocal.get()); } } public static void main(String[] args) { inheritableThreadLocal.set(24); for (int i = 0; i < 10; i++) { new Thread(new ThreadDemo()).start(); } } } |
执行代码会发现程序输出全是1024,这就是因为InheritableThreadLocal吧在主线程设置的值24传递到了那10个子线程中。
◆
InheritableThreadLocal原理剖析
◆
接下来我们来看一下InheritableThreadLocal为什么可以实现这种功能呢。
InheritableThreadLocal是ThreadLocal的子类,
与ThreadLocal相同的set方法
1 2 3 4 5 6 7 8 |
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } |
不同点是InheritableThreadLocal重写了createMap方法,将值赋值给了线程的inheritableThreadLocals变量。
1 2 3 |
void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } |
再跟进去Thread类的源码看inheritableThreadLocals变量你会发现:我去,这不是跟Threadlocal一样么,同样初始值为null,线程退出的时候清空。没错,就是这样的。也就是说它其实也是一个线程私有的变量,ThreadLocal的功能它是都有的。
那么它又是怎么把父线程的变量传递到子线程的呢?
接着看Thread的构造方法
1 2 3 |
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } |
一路追踪init方法你会看见这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } g.checkAccess(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID(); } |
仔细观察倒数第5行到倒数第二行你就明白了。
本文所有源码https://github.com/shiyujun/syj-study-demo
相关推荐
* InheritableThreadLocal 的实现原理和源码分析 * InheritableThreadLocal 与 ThreadLocal 的区别 * 使用 InheritableThreadLocal 需要注意的线程安全问题 * InheritableThreadLocal 在多线程编程中的应用场景和...
**其他并发工具类与锁原理**:这部分内容涉及ThreadLocal的工作原理、InheritableThreadLocal如何传递线程局部变量、CyclicBarrier和CountDownLatch的区别与用法、Semaphore信号量机制、CopyOnWriteArrayList如何...
### Java进阶知识点详解 #### 第一章:基本语法 ##### 关键字 - **static**:用于定义...以上是对Java进阶知识点的详细解析,覆盖了基本语法、JDK源码分析等多个方面,有助于深入理解Java语言的核心机制及高级特性。
基于openocd开源工具实现的C#桌面应用工具
精品-2025人工智能神经网络基本原理解析.pdf
施耐德ATV312变频器通过MCGS RTU通讯实现双机监控与控制的触摸屏集成解决方案,无PLC的施耐德ATV312变频器通讯示例:触摸屏控制监控两台变频器,功能多且省成本,改进型可调整步长 P&O MPPT(二区MPPT复现),光储系统MPPT 直流负载供电的单级离网光伏系统中,降压转器将太阳能光伏阵列和直流负载连接起来,同时确保最大功率点跟踪(MPPT) 和电池充电控制的良好运行。 在MPPT方面,提出了一种改进的自适应步长扰动观测(P&O)方法,以达到不同天气条件下太阳能光伏阵列的实际最大功率点(MPP),同时减少稳态振荡和功率损耗。 此外,电池充电控制侧使用三级充电控制器 (TSCC) 为铅酸电池站充电。 ,改进型P&O; 复现二区MPPT; 光储系统MPPT; 最大功率点跟踪(MPPT); 步长扰动观测; 降压转换器; 太阳能光伏阵列; 电池充电控制; 三级充电控制器(TSCC); 铅酸电池站。,改进型P&O MPPT技术,光储系统高效能量管理
redis学习脑图笔记
大学生创业项目源码
Spring Boot企业员工管理系统(包含万字论文+MYSQL)
对应博客地址:https://blog.csdn.net/u011561335/article/details/146312389
相关文章:https://blog.csdn.net/liu_23yanfeng/article/details/146319189
从春晚看科技技术-陈雄 - 公开版本.pptx
在计算机上安装制造装备物联及生产管理ERP系统软件来发挥其高效地信息处理的作用,可以规范信息管理流程,让管理工作可以系统化和程序化,同时,制造装备物联及生产管理ERP系统的有效运用可以帮助管理人员准确快速地处理信息。 制造装备物联及生产管理ERP系统在对开发工具的选择上也很慎重,为了便于开发实现,选择的开发工具为Eclipse,选择的数据库工具为Mysql。以此搭建开发环境实现制造装备物联及生产管理ERP系统的功能。其中管理员管理用户,新闻公告。 制造装备物联及生产管理ERP系统是一款运用软件开发技术设计实现的应用系统,在信息处理上可以达到快速的目的,不管是针对数据添加,数据维护和统计,以及数据查询等处理要求,制造装备物联及生产管理ERP系统都可以轻松应对。 关键词:制造装备物联及生产管理ERP系统;SpringBoot框架,系统分析,数据库设计
传统信息的管理大部分依赖于管理人员的手工登记与管理,然而,随着近些年信息技术的迅猛发展,让许多比较老套的信息管理模式进行了更新迭代,问卷信息因为其管理内容繁杂,管理数量繁多导致手工进行处理不能满足广大用户的需求,因此就应运而生出相应的问卷调查系统。 本问卷调查系统分为管理员还有用户两个权限,管理员可以管理用户的基本信息内容,可以管理新闻资讯信息以及新闻资讯的租赁信息,能够与用户进行相互交流等操作,用户可以查看问卷信息,可以查看新闻资讯以及查看管理员回复信息等操作。 该问卷调查系统采用的是WEB应用程序开发中最受欢迎的B/S三层结构模式,使用占用空间小但功能齐全的MySQL数据库进行数据的存储操作,系统开发技术使用到了JSP技术。该问卷调查系统能够解决许多传统手工操作的难题,比如数据查询耽误时间长,数据管理步骤繁琐等问题。总的来说,问卷调查系统性能稳定,功能较全,投入运行使用性价比很高。 关键词:问卷调查系统;MySQL数据库;SSM技术
VID20250317191237.mp4
西门子S7-1511 PLC PID控制阀门开度与模拟量转换——博途WinCC监控画面程序实践,西门子S7-1511 PLC PID控制阀门开度与模拟量转换——博途WinCC监控画面程序实践,matlab验证码识别系统,基于数字图像处理实现。 经过对图像的预处理、二值化、区域剪裁、数字定位、模板匹配法识别数字。 有gui界面和测试图像数据集。 ,核心关键词:Matlab验证码识别系统; 数字图像处理; 图像预处理; 二值化; 区域剪裁; 数字定位; 模板匹配法识别; GUI界面; 测试图像数据集。,基于Matlab的数字图像处理验证码识别系统
内容概要:本文提供了详细的 VMware 虚拟机安装指南,涵盖软件选择(Pro 和 Player 版区别)、安装步骤(适用于 Windows 和 Linux 主机系统)、虚拟机创建以及操作系统安装指导。详细介绍了配置虚拟机的各项关键设置,如资源分配、硬件参数定制、安装 VMware Tools 提升虚拟机性能和稳定性。并且列出了快照、克隆等高级功能的具体应用,还包括共享文件夹配置和几种常见错误的排除解决方案。 适合人群:初次接触虚拟化的用户和对虚拟环境搭建有一定兴趣的技术爱好者。 使用场景及目标:帮助用户快速部署自己的虚拟机,并掌握虚拟环境中常见的配置技巧,能够针对具体应用场景灵活地调整虚拟机的相关参数,提高工作效率,满足测试、学习、开发的需求。 其他说明:提供了一些安装过程可能遇到的问题及对应解决方案,在创建和维护过程中给予指导性的意见来确保用户的使用体验尽可能顺畅无阻,并给出了部分性能优化建议。
Matlab开发初学者视频教程,零基础入门,非常适合初学者。
质子交换膜燃料电池(PEMFC)Simulink模型:静态与动态模型分析及参数计算,基于Simulink的质子交换膜燃料电池模型:静态和动态模拟,计算输出和效率,cst仿真超表面 极化复用 ,核心关键词: 1. CST仿真 2. 超表面 3. 极化复用 以上信息以分号隔开,即为“CST仿真;超表面;极化复用”。,CST仿真超表面极化复用技术