难倒无数程序员的ThreadLocal原理,就这样被美团大牛轻松讲透彻

简介: 什么是ThreadLocal?ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。ThreadLocal怎么使用?ThreadLocl使用比较简单,主要有三个方法:get()、set()、remove()

什么是ThreadLocal?


ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。


ThreadLocal怎么使用?


ThreadLocl使用比较简单,主要有三个方法:get()、set()、remove()

相信不用我多说各位小伙伴也知道是什么意思

image.png

image.png


ThreadLocal底层原理


点开 ThreadLocal 的 set()方法

  • 首先是获取到当前线程
  • 调用getMap(t)方法获取到 ThreadLocalMap
  • 如果map不为null则进行set操作,如果为null则进行创建map操作

image.png

难倒无数程序员的ThreadLocal原理,就这样被美团大牛轻松讲透彻


点开get()方法


跟HashMap差不多,小伙伴们自己看看源码绝对能懂

image.png


点开remove()方法


跟HashMap差不多,找到索引,遍历又对因key就删除

image.png

细心的小伙伴可能就发现了这三个方法中都需要去获取:当前Thread和ThreadLocalMap

那么这是为什么呢???

我们接着看源码,点开getMap()方法

可以发现,ThreadLocalMap获取的是Thread中的一个对象

相信到这大家就明白了 ThreadLocal是怎么做到各个线程互不干扰的吧。

因为获取到的是当前线程的ThreadLocalMap,各个线程所以互不干扰。


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

image.png

小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,我们接着看


打开ThreadLocalMap的set方法


image.png

image.png

看过上面源码,熟悉HashMap的小伙伴们可能就发现了一个熟悉的身影——>Entry

但这个Entry就跟HashMap的有所不同,它继承了 WeakReference>

WeakReference:弱引用

而上面还有段代码不知道小伙伴们注意到没:tab[i] = new Entry(key, value);

这段代码意味着什么相信也不用我多说了吧,就是将key,value封装为 Entry节点;再根据map.set(this, value);这段代码可知,Key为当前的ThreadLocal对象,value为我们要set进来的值。

但是这里又有所重点:那就是对key调用了super(k),这里我暂且不多说,涉及到ThreadLocal的一个经典面试题,后面会进行详细讲解,小伙伴们要有耐心继续看哦!!!

image.png

到此基本结束,不要奇怪,ThreadLocal其实并不是太难,那我们先来做个总结!

总结

我们set进去的值,最终是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。


ThreadLocal相关面试题


ThreadLocal 如何解决 Hash 冲突?


与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

image.png

使用CAS,每次增加固定的值,所以采用的是线性探测法解决HasH冲突。

image.png

经典CAS

image.png


ThreadLocal内存泄漏问题及解决办法


ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。

image.png


解决办法


1. 使用完后记得remove

2.ThreadLocal自己提供了这个问题的解决方案。

每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。

image.png

3. 使用static修饰ThreadLocal

还可以使用 static 修饰ThreadLocal,保证ThreadLocal为强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

原因:根据可达性算法分析,类静态属性引用的对象可作为GC Roots根节点,即保证了ThreadLocal为不可回收对象


相关文章
|
Java 数据安全/隐私保护 Sentinel
面试官:Sentinel是如何实现限流的?
面试官:Sentinel是如何实现限流的?
1561 1
|
1月前
|
SQL XML Java
Mybatis基础使用知识(注解)
mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。 最后 mybatis 框架执行sql 并将结果映射为java对象并返回。采用ORM(对象关系映射)思想解决了实体和数据库映射问题,对jdbc进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api 打交道,就可以完成对数据库的持久化操作。
332 0
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
745 3
|
9月前
|
并行计算 安全 算法
量子计算在密码学中的应用与挑战:解密未来的安全
量子计算在密码学中的应用与挑战:解密未来的安全
475 6
|
自然语言处理 算法 Java
Java如何判断两句话的相似度?
【8月更文挑战第28天】Java如何判断两句话的相似度?
825 62
|
缓存 前端开发 JavaScript
阿里云CDN:怎么让网站变快
阿里云CDN:怎么让网站变快
|
Java 数据处理
|
并行计算 Java 调度
深入理解Java并发工具包中的CyclicBarrier
深入理解Java并发工具包中的CyclicBarrier
|
数据安全/隐私保护 安全
单点登录(SSO)看这一篇就够了
背景 在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。
277448 15
|
缓存 NoSQL API
分布式锁工具:Redisson,很强!
分布式锁工具:Redisson,很强!
627 1