文章目录

  • 前言
  • 一、ThreadLocal是什么
  • 二、Thread、ThreadLocal、ThreadLocalMap源码分析
  • 三、Thread、ThreadLocal、ThreadLocalMap三者关系
  • 四、ThreadLocal代码示例
  • 总结

前言

一提到ThreadLocal,很多人都会心惊胆战,因为ThreadLoca确实算是JAVA并发编程中一大难点,尤其是ThreadLocal、ThreadLocalMap、Thread三者之间的关系更是错综复杂。本文结合JAVA源码,皆以最简单的方式介绍这三者概念以及它们之间的关系,希望能帮读者打通这其中的任督二脉。


一、ThreadLocal是什么

之前一直有个观念,学习一个新的知识之前,必先了解其为什么诞生以及其解决的是什么问题。

之前我们学习多线程的时候就知道,多线程对共享变量进行访问,可以会出现安全问题。
ThreadLocal(线程变量)是解决线程安全问题一个很好的思路:它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,并发性更高。

在了解完ThreadLocal的基本概念之后,下面先用一张图展示ThreadLocal、ThreadLocalMap、Thread的关系。这里看不懂也没关系,下面会通过源码来介绍。


二、Thread、ThreadLocal、ThreadLocalMap源码分析

1.首先我们来看一下Thread类,可以看到里面有一个ThreadLocalMap类型的变量(这里有点坑,虽然这个变量名字叫threadLocals,但他是ThreadLocalMap类型)。

2.接下来我们看到ThreadLocal类,可以看到里面有一个ThreadLocalMap静态内部类,也就是上面Thread里面threadLocals变量将要指向的对象。
其实这里也是最容易迷惑的一点,即ThreadLocalMap是ThreadLocal类的静态内部类。
为什么要这样设计呢?
ThreadLocal通过给ThreadLocalMap使用默认的权限修饰符,使得ThreadLocalMap无法被其他包的类引用,最终将ThreadLocalMap完美地隐藏起来,同时ThreadLocal提供了一系列操作容器ThreadLocalMap的方法(get、set等),供外界使用。

通过这里我们可以小小总结一下:Thread类里面有一个ThreadLocalMap类型的变量,但是外界无法直接操作这个ThreadLocalMap,提供了一个工具箱ThreadLocal帮助我们操作ThreadLocal。

这样通过干巴巴的文字依然无法讲清楚这三者的关系,接下来我们看两个最重要的方法set 和 get看一下具体的流程。

我们先来看一下ThreadLocal中的set()方法,先获取当前线程,然后获取当前线程中的ThreadLocalMap(即threadLocals变量指向的),如果不为空,则把当前对象ThreadLocal作为键,外界传过来的参数作为值,保存ThreadLocalMap中。如果为空,则创建一个新的ThreadLocalMap。


接下来我们来看一下get方法,先获取当前线程对象,然后拿到当前线程对象的ThreadLocalMap,在根据键(即当前对象ThreadLocal)找到值。

如果我们在获取的时候当前线程对象的ThreadLocalMap为null时,则会执行setInitialValue()方法。
做了三件事:
1.创建map
2.给map设置一个键值对{threadLocal : initialValue}
3.返回initialValue,默认null


通过上面的源码我们可以知道,ThreadLocalMap以ThreadLocal作为键,值是我们调用ThreadLocal的set方法传进来的。

看到这里大家可能又乱了,这ThreadLocal、ThreadLocalMap、Thread关系到底是啥啊?现在来举个通俗易懂的例子。

例如:张三(线程1)和李四(线程2)去搬砖,有小白(ThreadLocal1)和小黑(ThreadLocal2)两个包工头,第一次它们报道找到了小白包工头,由于它们没有工资卡(ThreadLocalMap),小白给它们一人发了一张工资卡。第二天他们去找到了小黑包工头,可是他们已经有工资卡了,小黑就没有在给它们发工资卡(即一个线程只能有一个ThreadLocalMap)。

看到这个例子可以得出很多结论:
1.我们必须通过包工头拿到工资卡(即ThreadLocal是个工具箱,Thread要通过工具箱获取ThreadLocalMap)
2.一个人只能有一张工资卡(即一个Thread只能有一个ThreadLocalMap)。
3.对于张三和李四来说只需要拿到工资卡即可,至于是谁发的重要吗?(即无论使用哪个ThreadLocal创建ThreadLocalMap都不重要)。

但这个工资卡到底存储什么呢?
我们知道ThreadLocalMap以ThreadLocal为键,外界传入的value作为值,我们可以这样想:这个工资卡存储的就是为哪个包工头干了多少价值的活。
张三的工资卡可以为:小白(ThreadLocal1):200 ,小黑(ThreadLocal2):300。
李四的工资卡可以为:小白(ThreadLocal1):400 ,小黑(ThreadLocal2):500。

同时张三和李四可以去找更多的包工头干活,而他们两个并不会存在记混工资的情况,因为每个人都有每个人的工资卡呀(这就是线程隔离)!

从上面这一段再得出结论:
1.工资卡和包工头是独立的(虽然ThreadLocalMap是ThreadLocal的静态内部类,但完全可以当成两个独立实例)
2.工资卡以键值对形式存储数据,键是包工头,值是我们传进来的value。


三、Thread、ThreadLocal、ThreadLocalMap三者关系

1.ThreadLocal的作用有两个

 1.工具类,提供一系列方法操作ThreadLocalMap,比如get/set/remove2.隔离Thread和ThreadLocalMap,防止程序员直接创建ThreadLocalMap。自身的get/set内部会判断当前线程是否已经绑定一个ThreadLocalMap,有就继续用,没有就为其绑定。

2.虽然ThreadLocalMap是ThreadLocal的静态内部类,但它们的实例对象并不存在继承或者包裹关系。完全可以当成两个独立的实例。

3.一个Thread只能有一个ThreadLocalMap。

4.ThreadLocalMap以ThreadLocal为键存储数据。


四、ThreadLocal代码示例

下面用一段代码演示一下ThreadLocal的使用方法,加深理解。

public class TestThreadLocal {//创建两个ThreadLocal实例并指定泛型,分别存储Long/String类型数据private static ThreadLocal<Long> longLocal = new ThreadLocal<Long>();private static ThreadLocal<String> stringLocal = new ThreadLocal<String>();//set方法private void set() {longLocal.set(Thread.currentThread().getId());stringLocal.set(Thread.currentThread().getName());}//get方法private long getLong() {return longLocal.get();}//get方法private String getString() {return stringLocal.get();}public static void main(String[] args) throws InterruptedException {System.out.println("------main线程执行开始--------");final TestThreadLocal test = new TestThreadLocal();test.set();System.out.println(test.getLong());System.out.println(test.getString());Thread thread = new Thread() {public void run() {System.out.println("-------Thread-0线程执行开始--------");test.set();System.out.println(test.getLong());System.out.println(test.getString());System.out.println("-------Thread-0线程执行结束--------");}};thread.start();//thread.join():用来指定当前主线程等待其他线程执行完毕后,再来继续执行Thread.join()后面的代码thread.join();System.out.println(test.getLong());System.out.println(test.getString());System.out.println("------main线程执行结束--------");}
}

运行结果如下图所示:

main线程两次打印的中途,Thread-0线程开启并调用了test.set()进行设置。main线程和Thread-0设置的值肯定不同,但最终main线程前后打印结果一致。也就是说,main线程和Thread-0是线程隔离的,变量相互独立。

同时Thread-0线程和main线程共用了longLocal和stringLocal 两个ThreadLocal,但是却做到了线程隔离,可以看出线程隔离与ThreadLocal无关,ThreadLocal只是个工具人帮助操作而已,真正实现线程隔离的是线程内部唯一对应的那个ThreadLocalMap!


总结

个人认为理解ThreadLocal、ThreadLocalMap、Thread的关键就是它们是相辅相成的独立个体,Thread需要ThreadLocalMap实现线程隔离,而操作ThreadLocalMap又需要ThreadLocal,就是这么简单而已。至于为什么ThreadLocalMap的键是ThreadLocal,可以这样想:你(Thread)通过我(ThreadLocal)实现了某种目的(线程隔离),总要留下我的身影吧!


Thread、ThreadLocal、ThreadLocalMap是什么?看这一篇相关推荐

  1. 17万字 JUC 看这一篇就够了(一) (精华)

    JUC 今天我们来进入到 Java并发编程 JUC 框架的学习 ,内容比较多,但希望我们都能静下心来,耐心的看完这篇文章 文章目录 JUC 进程 概述 对比 线程 创建线程 Thread Runnab ...

  2. java后端做教育视频网站源码_【Java并发面试点】看这一篇应该是够了

    [Java并发面试点]看这一篇应该是够了 Java并发编程是Java后端.大数据开发面试必问项目之一,求职者务必掌握! Java并发面试点List 并发概念须知:进程与线程.同步与异步.并发与并行.阻 ...

  3. 17万字 JUC 看这一篇就够了(三) (精华)

    今天我们继续来学习Java并发编程 Juc框架 ,把剩余部分学习完 17万字 JUC 看这一篇就够了(一) (精华) 17万字 JUC 看这一篇就够了(二) (精华) 文章目录 非公原理 加锁 解锁 ...

  4. JVM难学?那是因为你没认真看完这篇文章

    JVM难学?那是因为你没认真看完这篇文章 一:虚拟机内存图解 JAVA程序运行与虚拟机之上,运行时需要内存空间.虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理. 虚拟机管理 ...

  5. Redis看这一篇就够了

    大数据时代NoSQL开始大行其道,其中常用于缓存的Redis可谓风头正盛,是大小公司技术架构中必不可少的一种中间件,也是职场技术同仁们必知必会的一种技术.本场Chat将从各个方面对Redis进行全面的 ...

  6. HashMap面试题,看这一篇就够了!

    在程序员这一职业中,集合是我们使用频率相当高的一个工具,而其中的 HashMap,则更是我们用以处理业务逻辑的好帮手,同时 HashMap 的底层实现和原理,也成了面试题中的常客. 还在担心面试中被问 ...

  7. 聊聊Java8之后的JDK升级内容(看这一篇就够了)

    聊聊Java8之后的JDK升级内容(看这一篇就够了) 背景 从 JDK 8 到 JDK 17 的新特性 JDK8 回顾 JDK9 JDK10 JDK11 JDK12 JDK13 JDK14 JDK15 ...

  8. 学习Nginx,看完这篇超详细的文章就够了

    目录 本文简介 一.Nginx的基本概念 1.1.Nginx是什么? 1.2.Nginx能帮助我们做些什么? 1.3.Nginx的特性 二.Nginx的安装 2.1.环境介绍 2.2.安装Nginx ...

  9. 看完这篇异地多活的改造,我决定和架构师battle一下

    点击"开发者技术前线",选择"星标" 让一部分开发者看到未来 1. 简述 异地多活的概念以及为什么要做异地多活这里就不进行概述了.概念性的很多,像什么同城双活. ...

最新文章

  1. plotly可视化绘制共享坐标轴图
  2. 运维人最爱的八本书,送给十一不出门的你
  3. 微信小程序---实现弹窗效果
  4. 实验报告: 线性表的基本操作及应用
  5. Java bitset转string_将java BitSet保存到DB
  6. 近百家公司高级运维的面试题汇总
  7. 集成LNMP/LAMP/FAMP的LuNamp2.1正式版开源发布
  8. imei 物联卡_物联卡设备信息判重(DoIotIsImeiExist)
  9. 解决 mklink 使用中的各种坑(硬链接,软链接/符号链接,目录链接)
  10. 畅通工程再续 最小生成树
  11. unity可以直接转h5吗_瞎折腾:用Unity撸纯HTML5移动游戏/应用
  12. win10计算机管理字体糊,完美解决:Win10系统字体模糊解决教程
  13. glibc2.31下的新double free手法/字节跳动pwn题gun题解
  14. 《自控力》第五章读书笔记
  15. 你的水杯里,藏着影响世界的顶级工艺
  16. 3万行代码硬撸一个一键发布文章工具,简直不要太好用,从此写文章,发文章,太简单了好伐
  17. Dell Inspiron15-7567 拆机插放内存条步骤
  18. poe交换机归类有什么?
  19. 红光光浴抗衰机制#大健康#红光光浴#红光#种光光学
  20. 踢球骨折在家day7

热门文章

  1. conda update -n base -c defaults conda
  2. 留守女子在家中身亡 2岁孙女7天无人照顾
  3. FANUC机器人的通讯方式(1)
  4. RV1109_RV1126 EVB板使用记录
  5. 快速了解华为端口聚合技术
  6. 青岛农业大学海都学院计算机专业怎么样,青岛农业大学海都学院就业率怎么样...
  7. 热文 | 卷积神经网络入门案例,轻松实现花朵分类
  8. 产品规划,你通常规划多久的时间线?
  9. JAVA SDK安装与使用
  10. js 删除对象里的某个属性