来自:冰河技术

前言

我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示。

对共享变量加锁虽然能够保证线程的安全,但是却增加了开发人员对锁的使用技能,如果锁使用不当,则会导致死锁的问题。而ThreadLocal能够做到在创建变量后,每个线程对变量访问时访问的是线程自己的本地变量

什么是ThreadLocal?

ThreadLocal是JDK提供的,支持线程本地变量。也就是说,如果我们创建了一个ThreadLocal变量,则访问这个变量的每个线程都会有这个变量的一个本地副本。如果多个线程同时对这个变量进行读写操作时,实际上操作的是线程自己本地内存中的变量,从而避免了线程安全的问题。

ThreadLocal使用示例

例如,我们使用ThreadLocal保存并打印相关的变量信息,程序如下所示。

public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args){//创建第一个线程Thread threadA = new Thread(()->{threadLocal.set("ThreadA:" + Thread.currentThread().getName());System.out.println("线程A本地变量中的值为:" + threadLocal.get());});//创建第二个线程Thread threadB = new Thread(()->{threadLocal.set("ThreadB:" + Thread.currentThread().getName());System.out.println("线程B本地变量中的值为:" + threadLocal.get());});//启动线程A和线程BthreadA.start();threadB.start();}
}

运行程序,打印的结果信息如下所示。

线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1

此时,我们为线程A增加删除ThreadLocal中的变量的操作,如下所示。

public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args){//创建第一个线程Thread threadA = new Thread(()->{threadLocal.set("ThreadA:" + Thread.currentThread().getName());System.out.println("线程A本地变量中的值为:" + threadLocal.get());threadLocal.remove();System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());});//创建第二个线程Thread threadB = new Thread(()->{threadLocal.set("ThreadB:" + Thread.currentThread().getName());System.out.println("线程B本地变量中的值为:" + threadLocal.get());System.out.println("线程B没有删除本地变量:" + threadLocal.get());});//启动线程A和线程BthreadA.start();threadB.start();}
}

此时的运行结果如下所示。

线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1
线程B没有删除本地变量:ThreadB:Thread-1
线程A删除本地变量后ThreadLocal中的值为:null

通过上述程序我们可以看出,线程A和线程B存储在ThreadLocal中的变量互不干扰,线程A存储的变量只能由线程A访问,线程B存储的变量只能由线程B访问。

ThreadLocal原理

首先,我们看下Thread类的源码,如下所示。

public class Thread implements Runnable {/***********省略N行代码*************/ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;/***********省略N行代码*************/
}

由Thread类的源码可以看出,在ThreadLocal类中存在成员变量threadLocals和inheritableThreadLocals,这两个成员变量都是ThreadLocalMap类型的变量,而且二者的初始值都为null。只有当前线程第一次调用ThreadLocal的set()方法或者get()方法时才会实例化变量。

这里需要注意的是:每个线程的本地变量不是存放在ThreadLocal实例里面的,而是存放在调用线程的threadLocals变量里面的。也就是说,调用ThreadLocal的set()方法存储的本地变量是存放在具体线程的内存空间中的,而ThreadLocal类只是提供了set()和get()方法来存储和读取本地变量的值,当调用ThreadLocal类的set()方法时,把要存储的值放入调用线程的threadLocals中存储起来,当调用ThreadLocal类的get()方法时,从当前线程的threadLocals变量中将存储的值取出来。

接下来,我们分析下ThreadLocal类的set()、get()和remove()方法的实现逻辑。

set()方法

set()方法的源代码如下所示。

public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//以当前线程为Key,获取ThreadLocalMap对象ThreadLocalMap map = getMap(t);//获取的ThreadLocalMap对象不为空if (map != null)//设置value的值map.set(this, value);else//获取的ThreadLocalMap对象为空,创建Thread类中的threadLocals变量createMap(t, value);
}

在set()方法中,首先获取调用set()方法的线程,接下来,使用当前线程作为Key调用getMap(t)方法来获取ThreadLocalMap对象,getMap(Thread t)的方法源码如下所示。

ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

可以看到,getMap(Thread t)方法获取的是线程变量自身的threadLocals成员变量。

在set()方法中,如果调用getMap(t)方法返回的对象不为空,则把value值设置到Thread类的threadLocals成员变量中,而传递的key为当前ThreadLocal的this对象,value就是通过set()方法传递的值。

如果调用getMap(t)方法返回的对象为空,则程序调用createMap(t, value)方法来实例化Thread类的threadLocals成员变量。

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

也就是创建当前线程的threadLocals变量。

get()方法

get()方法的源代码如下所示。

public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的threadLocals成员变量ThreadLocalMap map = getMap(t);//获取的threadLocals变量不为空if (map != null) {//返回本地变量对应的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//初始化threadLocals成员变量的值return setInitialValue();
}

通过当前线程来获取threadLocals成员变量,如果threadLocals成员变量不为空,则直接返回当前线程绑定的本地变量,否则调用setInitialValue()方法初始化threadLocals成员变量的值。

private T setInitialValue() {//调用初始化Value的方法T value = initialValue();Thread t = Thread.currentThread();//根据当前线程获取threadLocals成员变量ThreadLocalMap map = getMap(t);if (map != null)//threadLocals不为空,则设置value值map.set(this, value);else//threadLocals为空,创建threadLocals变量createMap(t, value);return value;
}

其中,initialValue()方法的源码如下所示。

protected T initialValue() {return null;
}

通过initialValue()方法的源码可以看出,这个方法可以由子类覆写,在ThreadLocal类中,这个方法直接返回null。

remove()方法

remove()方法的源代码如下所示。

public void remove() {//根据当前线程获取threadLocals成员变量ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)//threadLocals成员变量不为空,则移除value值m.remove(this);
}

remove()方法的实现比较简单,首先根据当前线程获取threadLocals成员变量,不为空,则直接移除value的值。

注意:如果调用线程一致不终止,则本地变量会一直存放在调用线程的threadLocals成员变量中,所以,如果不需要使用本地变量时,可以通过调用ThreadLocal的remove()方法,将本地变量从当前线程的threadLocals成员变量中删除,以免出现内存溢出的问题。

ThreadLocal变量不具有传递性

使用ThreadLocal存储本地变量不具有传递性,也就是说,同一个ThreadLocal在父线程中设置值后,在子线程中是无法获取到这个值的,这个现象说明ThreadLocal中存储的本地变量不具有传递性。

接下来,我们来看一段代码,如下所示。

public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args){//在主线程中设置值threadLocal.set("ThreadLocalTest");//在子线程中获取值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程获取值:" + threadLocal.get());}});//启动子线程thread.start();//在主线程中获取值System.out.println("主线程获取值:" + threadLocal.get());}
}

运行这段代码输出的结果信息如下所示。

主线程获取值:ThreadLocalTest
子线程获取值:null

通过上述程序,我们可以看出在主线程中向ThreadLocal设置值后,在子线程中是无法获取到这个值的。那有没有办法在子线程中获取到主线程设置的值呢?此时,我们可以使用InheritableThreadLocal来解决这个问题。

InheritableThreadLocal使用示例

InheritableThreadLocal类继承自ThreadLocal类,它能够让子线程访问到在父线程中设置的本地变量的值,例如,我们将ThreadLocalTest类中的threadLocal静态变量改写成InheritableThreadLocal类的实例,如下所示。

public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();public static void main(String[] args){//在主线程中设置值threadLocal.set("ThreadLocalTest");//在子线程中获取值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程获取值:" + threadLocal.get());}});//启动子线程thread.start();//在主线程中获取值System.out.println("主线程获取值:" + threadLocal.get());}
}

此时,运行程序输出的结果信息如下所示。

主线程获取值:ThreadLocalTest
子线程获取值:ThreadLocalTest

可以看到,使用InheritableThreadLocal类存储本地变量时,子线程能够获取到父线程中设置的本地变量。

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

由InheritableThreadLocal类的源代码可知,InheritableThreadLocal类继承自ThreadLocal类,并且重写了ThreadLocal类的childValue()方法、getMap()方法和createMap()方法。也就是说,当调用ThreadLocal的set()方法时,创建的是当前Thread线程的inheritableThreadLocals成员变量而不再是threadLocals成员变量。

这里,我们需要思考一个问题:InheritableThreadLocal类的childValue()方法是何时被调用的呢?这就需要我们来看下Thread类的构造方法了,如下所示。

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}Thread(Runnable target, AccessControlContext acc) {init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}public Thread(ThreadGroup group, Runnable target) {init(group, target, "Thread-" + nextThreadNum(), 0);
}public Thread(String name) {init(null, null, name, 0);
}public Thread(ThreadGroup group, String name) {init(group, null, name, 0);
}public Thread(Runnable target, String name) {init(null, target, name, 0);
}public Thread(ThreadGroup group, Runnable target, String name) {init(group, target, name, 0);
}public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {init(group, target, name, stackSize);
}

可以看到,Thread类的构造方法最终调用的是init()方法,那我们就来看下init()方法,如下所示。

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {/************省略部分源码************/if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}

可以看到,在init()方法中会判断传递的inheritThreadLocals变量是否为true,同时父线程中的inheritableThreadLocals是否为null,如果传递的inheritThreadLocals变量为true,同时,父线程中的inheritableThreadLocals不为null,则调用ThreadLocal类的createInheritedMap()方法。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}

在createInheritedMap()中,使用父线程的inheritableThreadLocals变量作为参数创建新的ThreadLocalMap对象。然后在Thread类的init()方法中会将这个ThreadLocalMap对象赋值给子线程的inheritableThreadLocals成员变量。

接下来,我们来看看ThreadLocalMap的构造函数都干了啥,如下所示。

private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];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) {//调用重写的childValue方法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++;}}}
}

在ThreadLocalMap的构造函数中,调用了InheritableThreadLocal类重写的childValue()方法。而InheritableThreadLocal类通过重写getMap()方法和createMap()方法,让本地变量保存到了Thread线程的inheritableThreadLocals变量中,线程通过InheritableThreadLocal类的set()方法和get()方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。此时,如果父线程创建子线程,在Thread类的构造函数中会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量中。

最后,附上并发编程需要掌握的核心技能知识图,祝大家在学习并发编程时,少走弯路。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!相关推荐

  1. 面试又被问高并发,哑口无言?一份高并发核心文档助你吊打面试官

     关于程序员,除了做项目来提高自身的技术之外,还有一种提升自己的专业技能就是:多!看!书! Java高并发程序设计 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素 ...

  2. 执行完execute和update后存储过程变成invalid_学会反射后,我被面试官录取了(干货)

    反射是一个非常重要的知识点,在学习Spring 框架时,Bean的初始化用到了反射,在破坏单例模式时也用到了反射,在获取标注的注解时也会用到反射······ 当然了,反射在日常开发中,我们没碰到过多少 ...

  3. 看了这个文章你也可以和面试官侃半个小时hashMap了

    HashMap应该算是Java后端工程师面试的必问题,因为其中的知识点太多,很适合用来考察面试者的Java基础. 面试官: 你先自我介绍一下吧! 我: 我是安琪拉,草丛三婊之一,最强中单(钟馗不服)! ...

  4. threadlocal存连接对象的目的_面试官:知道ThreadLocal嘛?谈谈你对它的理解?

    在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的方式有很多种,可能是循序渐进也可能是就像我的题目那样,因此只有理解透彻了,不管怎么问,都能游刃有余. 这篇文章主要从以 ...

  5. 高并发中的 限流、熔断、降级、预热、背压你都知道是什么意思吗?

    首先,我们需要明确一下这几个名词出现的场景:分布式高并发环境.如果你的产品卖相不好,没人鸟它,那它就用不着这几个属性.不需要任何加成,低并发系统就能工作的很好. 分布式系统是一个整体,调用关系错综复杂 ...

  6. 堪称神级的阿里巴巴“高并发”教程《基础+实战+源码+面试+架构》

    前言 作为一个普普通通的程序员,如何才能提升自己的能力,在职场上拥有一技之长,这也成为普通的你我,迫切的需求. 拥有什么样的能力才能不被淘汰?答案是:高并发,它几乎成为了每个程序员都想要拥有的经验. ...

  7. 29W 字总结阿里 Java 高并发编程:案例 + 源码 + 面试 + 系统架构设计

    下半年的跳槽季已经开始,好多同学已经拿到了不错的 Offer,同时还有一些同学对于 Java 高并发编程还缺少一些深入的理解,不过不用慌,今天老师分享的这份 27W 字的阿里巴巴 Java 高并发编程 ...

  8. 面试官:为什么单线程的Redis可以实现高并发访问

    背景 上回说到小枫在接受面试官的拷打,所幸第一个问题回答的还不错,因此面试官对于小枫的初步印象还行.我们接着来看看小枫是怎么和面试官继续过招的吧,他还能扛得住面试官几个连环炮呢? 面试官考察目的分析 ...

  9. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

最新文章

  1. 3-runtime 之 Tagged Pointer
  2. 使用 Gatsby.js 搭建静态博客 EX 使用语雀发布到博客
  3. 干货!图神经网络及其自监督学习
  4. mysql druid 多数据源_SpringBoot使用阿里数据库连接池Druid以及多数据源配置
  5. 牛津花卉数据集贴标签分类
  6. qt框架的开发模式_Flutter 混合开发框架模式探索
  7. 【学习笔记】《数据挖掘:理论与算法》CH4神经网络
  8. 自学问题 zx yl
  9. NCRE四级网络工程师考题详解----三级索引结构
  10. 四色着色问题 c语言编程,数据结构-图着色问题
  11. Mysql启动自己主动设置max_connections为其它值
  12. Matlab--view函数详解
  13. Python自带又好用的代码调试工具Pdb学习笔记
  14. 设计模式 C++外观者模式
  15. 《设计模式详解》创建型模式 - 单例模式
  16. vue 后端返回图片乱码处理方法
  17. 大话机器学习之数据预处理与数据筛选
  18. Atitit 利用前端cache indexdb localStorage 缓存提升性能优化attilax总结 1.1. indexdb 更加强大点,但是结果测试,api比较繁琐 使用叫麻烦些 1
  19. pytorch+cuda+cudnn下载、安装和配置
  20. ARRIS路由器梅林系统救砖

热门文章

  1. 【Java】多线程编程(并发编程)基础(上)
  2. HDU3440(差分约束+SPFA算法)
  3. 文件转换html文件失败,pandoc文档转换失败,出现错误67
  4. java二叉树镜像_给定一个二叉树,检查它是否是镜像对称的。
  5. 0x54. 动态规划 - 树形DP(习题详解 × 12)
  6. 牛客练习赛81 B. 小 Q 与彼岸花(FWT nlogn做法)
  7. 【学习笔记】超简单的快速傅里叶变换(FFT)(含全套证明)
  8. smack连接远程openfire连接超时-No response received within reply timeout. Timeout was 5000ms (~5s).
  9. 怎么判断膝关节错位_路走多了,膝盖疼是怎么回事?
  10. python使用函数的目的_在Python 3.x中经常看到定义函数有一个单独的 * 参数?定义这样参数的目的是?怎样对其取值呢?...