作者:Sicimike

blog.csdn.net/Baisitao_/article/details/100063561

前言

相信很多同学都听过ThreadLocal,即使没用过也听过。但是要仔细一问ThreadLocal是个啥,很多同学也不一定能说清楚。本篇博客就是为了回答关于ThreadLocal的一系列灵魂拷问:ThreadLocal是个什么?怎么用?为什么要用它?它有什么缺点?怎么避免…

ThreadLoacl是什么

在了解ThreadLocal之前,我们先了解下什么是线程封闭

把对象封闭在一个线程里,即使这个对象不是线程安全的,也不会出现并发安全问题。

实现线程封闭大致有三种方式:

  • Ad-hoc线程封闭:维护线程封闭性的职责完全由程序来承担,不推荐使用

  • 栈封闭:就是用(stack)来保证线程安全

public void testThread() {StringBuilder sb = new StringBuilder();sb.append("Hello");
}

StringBuilder是线程不安全的,但是它只是个局部变量,局部变量存储在虚拟机栈虚拟机栈是线程隔离的,所以不会有线程安全问题

  • ThreadLocal线程封闭:简单易用

第三种方式就是通过ThreadLocal来实现线程封闭,线程封闭的指导思想是封闭,而不是共享。所以说ThreadLocal是用来解决变量共享的并发安全问题,多少有些不精确。

使用

JDK1.2开始提供的java.lang.ThreadLocal的使用方式非常简单

public class ThreadLocalDemo {public static void main(String[] args) throws InterruptedException {final ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("main-thread : Hello");Thread thread = new Thread(() -> {// 获取不到主线程设置的值,所以为nullSystem.out.println(threadLocal.get());threadLocal.set("sub-thread : World");System.out.println(threadLocal.get());});// 启动子线程thread.start();// 让子线程先执行完成,再继续执行主线thread.join();// 获取到的是主线程设置的值,而不是子线程设置的System.out.println(threadLocal.get());threadLocal.remove();System.out.println(threadLocal.get());}
}

运行结果

null
sub-thread : World
main-thread : Hello
null

运行结果说明了ThreadLocal只能获取本线程设置的值,也就是线程封闭。基本上,ThreadLocal对外提供的方法只有三个get()、set(T)、remove()。

原理

使用方式非常简单,所以我们来看看ThreadLocal的源码。ThreadLocal内部定义了一个静态ThreadLocalMap类,ThreadLocalMap内部又定义了一个Entry类,这里只看一些主要的属性和方法

public class ThreadLocal<T> {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();}public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}// 从这里可以看出ThreadLocalMap对象是被Thread类持有的ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}// 内部类ThreadLocalMapstatic class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;// 内部类Entity,实际存储数据的地方// Entry的key是ThreadLocal对象,不是当前线程ID或者名称Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}// 注意这里维护的是Entry数组private Entry[] table;}
}

根据上面的源码,可以大致画出ThreadLocal在虚拟机内存中的结构

实线箭头表示强引用,虚线箭头表示弱引用(关于对象的四种引用,可以参考博主之前的博客:Java中四种引用)。需要注意的是:

  • ThreadLocalMap虽然是在ThreadLocal类中定义的,但是实际上被Thread持有。

  • Entry的key是(虚引用的)ThreadLocal对象,而不是当前线程ID或者线程名称。

  • ThreadLocalMap中持有的是Entry数组,而不是Entry对象。

对于第一点,ThreadLocalMap被Thread持有是为了实现每个线程都有自己独立的ThreadLocalMap对象,以此为基础,做到线程隔离。第二点和第三点理解,我们先来想一个问题,如果同一个线程中定义了多个ThreadLocal对象,内存结构应该是怎样的?此时再来看一下ThreadLocal.set(T)方法:

 public void set(T value) {// 获取当前线程对象Thread t = Thread.currentThread();// 根据线程对象获取ThreadLocalMap对象(ThreadLocalMap被Thread持有)ThreadLocalMap map = getMap(t);// 如果ThreadLocalMap存在,则直接插入;不存在,则新建ThreadLocalMapif (map != null)map.set(this, value);elsecreateMap(t, value);}

也就是说,如果程序定义了多个ThreadLocal,会共用一个ThreadLocalMap对象,所以内存结构应该是这样

这个内存结构图解释了第二点和第三点。假设Entry中key为当前线程ID或者名称的话,那么程序中定义多个ThreadLocal对象时,Entry数组中的所有Entry的key都一样(或者说只能存一个value)。ThreadLocalMap中持有的是Entry数组,而不是Entry,则是因为程序可定义多个ThreadLocal对象,自然需要一个数组。

内存泄漏

ThreadLocal会发生内存泄漏吗?

仔细看下ThreadLocal内存结构就会发现,Entry数组对象通过ThreadLocalMap最终被Thread持有,并且是强引用。也就是说Entry数组对象的生命周期和当前线程一样。即使ThreadLocal对象被回收了,Entry数组对象也不一定被回收,这样就有可能发生内存泄漏。ThreadLocal在设计的时候就提供了一些补救措施:

  • Entry的key是弱引用的ThreadLocal对象,很容易被回收,导致key为null(但是value不为null)。所以在调用get()、set(T)、remove()等方法的时候,会自动清理key为null的Entity。

  • remove()方法就是用来清理无用对象,防止内存泄漏的。所以每次用完ThreadLocal后需要手动remove()。

有些文章认为是弱引用导致了内存泄漏,其实是不对的。假设把弱引用变成强引用,这样无用的对象key和value都不为null,反而不利于GC,只能通过remove()方法手动清理,或者等待线程结束生命周期。也就是说ThreadLocalMap的生命周期由持有它的线程来决定,线程如果不进入terminated状态,ThreadLocalMap就不会被GC回收,这才是ThreadLocal内存泄露的原因。

应用场景

  • 维护JDBC的java.sql.Connection对象,因为每个线程都需要保持特定的Connection对象。

  • Web开发时,有些信息需要从controller传到service传到dao,甚至传到util类。看起来非常不优雅,这时便可以使用ThreadLocal来优雅的实现。

  • 包括线程不安全的工具类,比如Random、SimpleDateFormat等

与synchronized的关系

有些文章拿ThreadLocal和synchronized比较,其实它们的实现思想不一样。

  • synchronized是同一时间最多只有一个线程执行,所以变量只需要存一份,算是一种时间换空间的思想

  • ThreadLocal是多个线程互不影响,所以每个线程存一份变量,算是一种空间换时间的思想

总结

ThreadLocal是一种隔离的思想,当一个变量需要进行线程隔离时,就可以考虑使用ThreadLocal来优雅的实现。

  • Springboot启动扩展点超详细总结,再也不怕面试官问了

  • List去除重复数据的五种方式,学到了...

  • SpringBoot操作ES进行各种高级查询(必须收藏)

  • 10w行级别数据的Excel导入优化记录

  • 再见,HttpClient!再见,Okhttp!

  • 使用Docker部署SpringBoot+Vue系统

  • 快试试Java8中的StringJoiner吧,真香!

点击阅读全文前往微服务电商教程

趣头条面试题:ThreadLocal是什么?怎么用?为什么用它?有什么缺点相关推荐

  1. 面试完还呗、拼多多、蚂蚁金服、趣头条、京东到家之后,我知道了这些

    最近一个朋友,在谋求架构师岗位的工作,经历了魔都的一批互联网公司的洗礼,让他把面试经历整理了一下,给大家一些经验吧,希望各位后面去这些公司面试的时候,能有些心理准备. 还呗 地点:2号线金科路地铁站( ...

  2. 从字节跳动离职后,拿到探探、趣头条、爱奇艺、小红书、15家公司的 offer

    前言 博主目前从事Android开发3年,前两年一直在抖音工作.我这篇文章并不是简单的描述一些面试中的题,或者总结一些Android的知识,而是想记录我整个的想法和准备的过程,以及一些心得体会,让大家 ...

  3. 土木工程转专业,上岸趣头条、今日头条后端开发,学习历程分享

    学习群小伙伴的学习面试经历,在去年刚转专业开始学习的时候给到他路线和学习过程中的帮助,所以他在学习过程中基本没走弯路,执行力也很强.里面提到的很多学习上的点也是我在文章中一直反复强调的,其他同学可以借 ...

  4. 【每日面试】2021趣头条Java一面

    作者:堆栈溢出 链接:https://www.nowcoder.com/discuss/807771?type=0&order=7&pos=3&page=1&sourc ...

  5. 趣头条基于Flink+ClickHouse的实时数据分析平台

    导读:趣头条一直致力于使用大数据分析指导业务发展.目前在实时化领域主要使用 Flink+ClickHouse 解决方案,覆盖场景包括实时数据报表.Adhoc 即时查询.事件分析.漏斗分析.留存分析等精 ...

  6. clickhouse 航空数据_趣头条基于Flink+ClickHouse的实时数据分析平台

    原标题:趣头条基于Flink+ClickHouse的实时数据分析平台 分享嘉宾:王金海 趣头条 编辑整理:王彦 内容来源:Flink Forward Asia 出品平台:DataFunTalk 导读: ...

  7. 趣头条将获得阿里1.71亿美元的可转债,为期三年...

    3月29日消息,昨日晚间,趣头条宣布获得阿里巴巴1.71亿美元投资.趣头条在公告中表示,根据协议,趣头条将获得阿里1.71亿美元的可转债,可转债年息为3%,为期三年.届时阿里有权将这笔可转债转换成趣头 ...

  8. 再向“乡镇青年”下沉,“资讯界拼多多”趣头条也难成为另一个拼多多

    文|孔虬 来源|潇湘财经(XiaoxiangFin) 趣头条还是上市了,也是在纳斯达克.而且首日收涨128.43%,期间5次暂停交易,市值最高超过58亿美元. 这股价走势像极了拼多多.因为" ...

  9. 如何利用扩展欧几里得算法求解不定方程_客户端不用的算法系列:从头条笔试题认识扩展欧几里得算法...

    难度较高,阅读时间大概 28 分钟 这是数论的第二篇,在<素数筛法>中,我们重温了素数这个数学定义,并且给出了区别于教科书上更高效的 Eratosthenes 筛法和欧拉线性筛.这篇文会从 ...

  10. 实时平台在趣头条的建设实践

    原创: 席建刚 本文由趣头条实时平台负责人席建刚分享趣头条实时平台的建设,整理者叶里君.文章将从平台的架构.Flink现状,Flink应用以及未来计划四部分分享. 一.平台架构 1.Flink 应用时 ...

最新文章

  1. 两个排序数组的中位数(4.Median of Two Sorted Arrays)
  2. java读写文件大全
  3. 是否是一个新的机会?
  4. boost::gregorian模块实现计算今年的元旦直到下一个元旦的日子的测试程序
  5. Web安全之点击劫持(ClickJacking)
  6. raster | 多图层栅格对象的一些处理方法
  7. c语言中文网_在C语言中使用中文字符
  8. day23 内置函数,匿名函数,递归
  9. Redis定时任务,
  10. NVIDIA的python-GPU算法生态 ︱ RAPIDS 0.10
  11. python多进程共享变量,附共享图像内存实例
  12. Python 的解释器
  13. 程序员要么在变来变去中成长,要么在变来变去中被淘汰,要么主动去适应变来边去的事实...
  14. 落后产能的实现路径 | 凌云时刻
  15. C#中Dictionary的用法及用途
  16. QT5的软键盘输入法实现
  17. Android开发笔记之SeekBar 时间的显示 快进快退 Mediaplayer
  18. mysql中的不等于
  19. 闫令琪:Games101 现代计算机图形学-光线追踪(三):渲染方程和路径追踪path ray tracing 作业Assignment07解析
  20. mysql中week()函数的用法

热门文章

  1. Arduino温控风扇
  2. Solr使用入门指南
  3. 激活函数maxout
  4. 校园网破解|校园网wifi破解|校园网免认证教程-SaoPanel
  5. VREP学习记录(持续更新)
  6. CRC8/CRC16/CRC32最全总结
  7. 移动端车牌识别sdk——技术干货
  8. CRT使用(二)CRT软件修改超时时间
  9. gitlab中创建项目组及项目
  10. 深度学习与计算机视觉教程(1) | 引言与知识基础(CV通关指南·完结)