前言

java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。如下图所示:

其中堆是占虚拟机中内存最大的,堆被所有线程所共享,其最主要的便是存放实例对象。也因为堆内存是共享的,因此在多线程操作的条件下,多线程中堆内存中的数据十分容易发生线程安全的问题。因此为了保证多个线程对变量的安全访问,我们可以将变量放到ThreadLocal对象中,变量在每个线程中都有独立值,线程只能操作自己的变量,访问不到其他线程中的变量。

ThreadLocal

ThreadLocal顾名思义便是线程本地变量的意思,在JAVA程序中每new一个ThreadLocal对象实例时,每个线程就会有一个隶属于自己的变量,一个专属于线程的变量,也因此该变量不会被其他的线程访问到,以此来规避了线程安全的问题。

那么ThreadLocal如何使得每个线程拥有自己独有的本地值呢?

在JDK8的版本中,每一个线程都有一个属于自己的ThreadLocalMap,ThreadLocalMap随着Thread的创建而存在,随着Thread的实例销毁而销毁。

        //ThreadLocalMap其中一个构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

而ThreadLocalMap即是保存本地变量的关键之一,它首先以ThreadLocal实例和变量值作为Entry对象的构造参数来构造Entry对象,后以ThreadLocal实例进行散列计算hash,在散列函数计算后,每个ThreadLocal会均匀地、独立地被分布在Entry数组中,也就是会得到自己在Entry数组中的索引值,然后用此索引将构造出来的Entry对象放入到Entry数组中。也由于每个线程都有自己的ThreadLocalMap,因此变量值是存放在专属于自己线程的ThreadLocalMap中,这个ThreadLocalMap其他线程获取不到,所以每个线程都有专属于自己的变量值,在操作的时候也是对自己专属的变量值进行操作。

从上图我们也可以知道ThreadLocalMap其实是由ThreadLocal来进行管理的,两者的关系密不可分。

我们在平时的操作中,大多是操作ThreadLocal的get(),set()方法,似乎ThreadLocalMap接触的较少,但在接下来深入ThreadLocal的时候,我们会发现ThreadLocal这个类其实是基于ThreadLocalMap来完成的。

ThreadLocal的get方法

    /**
* 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();
}

上面的代码块展示的是ThreadLocal中get方法的源码,通过源码我们可以了解到get方法有以下步骤:

  1. 首先它会获取当前占有CPU时间片的线程的实例,然后通过当前线程的实例调用getMap()方法来获取当前线程的ThreadLocalMap。
 //getMap()方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
  1. 如果ThreadLocalMap不为空的话,就以自身ThreadLocal实例作为参数调用ThreadLocalMap的getEntry()方法来获取到Entry对象,如果Entry对象不为空,则获取Entry的value属性值返回。
  2. 如果map为空的话或者此ThreadLocal实例计算出的hash值最为Entry数组的索引在Entry数组中并未存在Entry对象,证明当前线程并未初始化ThreadLocalMap,调用setInitialValue方法后返回。

ThreadLocal的setInitialValue方法

 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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}

我们来探寻get()方法中调用的setInitialValue方法,在以下代码中我们可以知道:

  1. 首先一上来就会调用一个钩子函数initialValue()来给value变量赋值,但是我们进入initialValue()方法却发现这个方法的返回值是null,如果需要继承ThreadLocal来重写这个方法就太麻烦,JDK已经为大家定义了ThreadLocal的内部SuppliedThreadLocal静态子类,并且提供了ThreadLocal.withInitial()静态工厂方法,我们只需要在定义一个ThreadLocal类型变量时,使用这个方法。

initialValue钩子函数只会调用一次,且只在不使用ThreadLocal.set()方法去设置值就使用ThreadLocal.get()方法去获取值的时候,会执行。

      //钩子函数
protected T initialValue() {
return null;
} //静态工厂方法
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
  1. get方法的步骤一致,也是获取当前的线程后,获取当前线程的ThreadLocalMap,如果没有的话则创建为ThreadLocalMap创建一个map,这是因为一开始Thread下面的ThreadLocalMap初始值为空,所以有create这个步骤。在creatMap的方法中,我们可以看到了新建了一个ThreadLocalMap类,并以当前的ThreadLocal实例对象和initialValue产生的值作为构造参数,以此生成Entry对象保存在Entry数组中。
       void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
  1. 最后方法返回。

ThreadLocal的set方法

    public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}

以上是ThreadLocal的set方法的源码,它相对于get方法较简单,也是获取当前线程并获取当前线程的ThreadLocalMap,如果有的话调用ThreadLocalMap的set方法将值设置进去,如果ThreadLocal为空,则使用createMap方法去创建一个ThreadLocalMap。

ThreadLocal的remove方法

     public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}

ThreadLocal的remove方法便更简单了,它仅判断获取到的当前线程的ThreadLocalMap不为空,则调用了ThreadLocalMap的remove方法去删除值。

后续

从以上的ThreadLocal函数,我们可以看到,许多重要的方法都是依靠着ThreadLocalMap及其api去完成对ThreadLocal方法的实现的,不难看出理解ThreadLocalMap其实相较于理解ThreadLocal是比较重要的,而ThreadLocalMap内部对Entry这个子类的实现,更是考虑到了ThreadLocal的内存泄漏,因此使用了WeakReference弱引用去关联ThreadLocal实例,防止强引用导致的内存泄露的问题。

对ThreadLocalMap我会另开一个随笔去写,请多多担待。

结尾

本文参考

[1] 周志明.深入理解Java虚拟机:JVM高级特性与最佳实践.-2版.北京:机械工业出版社,2013.6

[2] 尼恩.Java高并发编程.卷2,多线程、锁、JMM、JUC、高并发设计模式.北京:机械工业出版社,2021,5

细说ThreadLocal(一)的更多相关文章

  1. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  2. 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲

    前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...

  3. ThreadLocal、Volatile、synchronized、Atomic

    前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...

  4. 死磕Java之聊聊ThreadLocal源码(基于JDK1.8)

    记得在一次面试中被问到ThreadLocal,答得马马虎虎,所以打算研究一下ThreadLocal的源码 面试官 : 用过ThreadLocal吗? 楼主答 : 用过,当时使用ThreadLocal的 ...

  5. 聊聊ThreadLocal源码(基于JDK1.8)

    原文:https://cloud.tencent.com/developer/article/1333298 聊聊JDK源码中ThreadLocal的实现 主要方法: ThreadLocal的get方 ...

  6. Java面试必问:ThreadLocal终极篇 淦!

    点赞再看,养成习惯,微信搜一搜[敖丙]关注这个互联网苟且偷生的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系列 ...

  7. ThreadLocal源码深度剖析

    ThreadLocal源码深度剖析 ThreadLocal的作用 ThreadLocal的作用是提供线程内的局部变量,说白了,就是在各线程内部创建一个变量的副本,相比于使用各种锁机制访问变量,Thre ...

  8. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  9. 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密

    0x00 前言 在匹夫的上一篇文章<匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置>的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识.但是觉得还是不够过瘾,很多需要说清楚 ...

随机推荐

  1. 数字图像处理(一)之灰度转换和卷积python实现

    使用Python实现数字图像处理中如下功能: 彩色图像转成灰度图像 实现图像的相关&卷积操作 实现图像的高斯核卷积 使用的库和python版本如下: imageio:2.9.0 用于读取磁盘中 ...

  2. 在Windows Server 2012R2离线安装.net framework3.5

    最近新装了一台Windows Server 2012 R2的服务器,安装数据库时,出现了提示安装不上 .net framework3.5的情况,经过网络上多次的资料查找及反复试验终于找到了一个可以解决 ...

  3. bzoj2037 Sue的小球(区间dp,考虑到对未来的贡献)

    ​​​​​​​​​​​​​​大致意思就是现在你要不断的奔跑到不同的地点去接球,每一秒可以移动一个单位长度,而你接到一个球的动作是瞬间的,收益是y[i]-t*v[i] 然后呢,要求分数最高. 起初看这个 ...

  4. Golang/Java 实现无重复字符的最长子串 - LeetCode 算法

    给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度. 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-subs ...

  5. C++ cin和while cin

    int main(){ string input; vector<string> arr; while(cin >> input) { cout << " ...

  6. 【c++ Prime 学习笔记】第13章 拷贝控制

    定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...

  7. 合理占用服务器空闲GPU[狗头]

    合理占用服务器GPU资源[狗头] 场景:当你想进行模型训练时,发现GPU全被占用,怎么办? 解决方案1: 在终端输入如下命令:watch -n 设定刷新时间(s) nvidia-smi 然后记起来了回 ...

  8. [对对子队]Alpha阶段项目展示博客

    Alpha阶段项目展示博客 1 团队成员的简介和个人博客地址 成员 头像 岗位 博客 个人介绍 黄贤昊 PM 17373253 喜欢玩游戏和做游戏,项目经验基本都和游戏相关,擅长摸鱼,偶尔敬业. 刘子 ...

  9. [技术博客] K-Means算法

    遇到的问题 在对微软\(OCR\)的\(api\)进行测试的过程中,我发现有时候它并不能分析出一个表格的形态,也就是说不知道每个文本对应在表格中的第几行第几列.但是它可以较为准确的给出这些文本的坐标. ...

  10. Qt学习-模仿Qt实现一个colorbutton

    1.mycolorbutton.h #include<QToolButton> #include<QtGlobal> #include<QColor> class ...