文章目录

  • 用法
    • Example1
    • Example2
  • Springboot @Transcation 注解的原理
  • Entry 的 Key 设置为弱引用有什么好处
  • 内存泄漏问题

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

该类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问一个变量(通过其 get 或 set 方法)的线程都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,希望将状态与线程关联(例如,用户 ID 或事务 ID)。

每个线程中维护一个 ThreadLocalMap,ThreadLocalMap 虽然名字里有 Map,实际上是 Entry 数组。Entry 数组中 Entry 的 key 是 ThreadLocal,Value 是 保存的属性。下面以一段代码举例:

    public static void main(String[] args) {        ThreadLocal<Integer> tl = new ThreadLocal<>();        tl.set(1);    }

用法

Example1

import java.util.concurrent.TimeUnit;public class ThreadLocal2 {static ThreadLocal<Person> tl = new ThreadLocal<>();public static void main(String[] args) {new Thread(()->{try {//先睡两秒TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(tl.get());}).start();new Thread(()->{try {//先睡一秒TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}tl.set(new Person());}).start();}static class Person {String name = "zhangsan";}
}

输出:

null

Example2

import java.util.concurrent.atomic.AtomicInteger;public class ThreadId {// Atomic integer containing the next thread ID to be assignedprivate static final AtomicInteger nextId = new AtomicInteger(0);// Thread local variable containing each thread's IDprivate static final ThreadLocal<Integer> threadId =ThreadLocal.withInitial(nextId::getAndIncrement);// Returns the current thread's unique ID, assigning it if necessarypublic static int get() {return threadId.get();}

输出:

get() = 0

Springboot @Transcation 注解的原理

@Transcation 注解实现原理是基于 AOP 编程思想。AOP 编程思想是将与业务无关的功能抽离出来,并在业务处理前后进行插入。在 Springboot 源码中,@Transcation 注解就是使用 AOP 编程思想实现的。
当一个方法上有 @Transcation 注解时,在 Springboot 源码中会为这个方法生成一个代理对象。在这个代理对象中,会添加两个通知:前置通知和后置通知。前置通知会在真实方法执行前执行,后置通知会在真实方法执行后执行。
在前置通知中,会开启一个事务,并将事务挂起。在真实方法执行完毕后,会在后置通知中提交事务。如果真实方法抛出异常,则会在后置通知中回滚事务。
这样,当一个方法上有 @Transcation 注解时,方法的执行过程就会被包装在一个事务中,从而可以实现事务的控制。
在 Spring 中,每个线程都有一个 ThreadLocal 对象,可以用来存储线程私有的数据。在 Spring 的事务管理器中,会将当前线程所使用的数据源放入 ThreadLocal 对象中。所以,在前置通知中,可以从 ThreadLocal 对象中获取当前线程所使用的数据源。
获取数据源:

DataSource dataSource = TransactionSynchronizationManager.getResource(dataSource);

设置数据源:

Entry 的 Key 设置为弱引用有什么好处

我们先假设不是弱引用,是强引用。当 tl 在外部的强引用被回收以后,因为 ThreadLocalMap 的 Entry 的 Key 还是强引用,只要线程不结束,这个 ThreadLocal 一直不会被回收。
如果是弱引用,tl 在外部的强引用被回收以后,GC 时就会回收这个弱引用的 ThreadLocal 对象。但是此时 Value 还没有被回收,这就导致了内存泄漏问题。

内存泄漏问题

ThreadLocal 类本身并不会导致内存泄漏。但是,如果使用不当,可能会导致内存泄漏。上文中就描述了 Value 没有被回收导致的内存泄漏问题,那么我们看什么时候 Value 会被回收。

  1. 线程结束,ThreadLocalMap 清除
    由于在 JAVA 中线程对象与 ThreadLocal 对象绑定在一起,所以只要线程没了,空间自然回收。因为线程对象回收时,ThreadLocalMap 的强引用也没了,所以内部全部回收。
  2. 线程未结束
    1. 调用 remove 方法,释放 Entry 的内存。
    2. 调用 get 和 set 方法时,释放 key == null 的 Entry 的内存。

remove、get和set方法都会调用 expungeStaleEntry 方法来释放 value 的内存。

           // expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;

下面是 expungeStaleEntry 方法的源码。
Expunge a stale entry by rehashing any possibly colliding entries lying between staleSlot and the next null slot. This also expunges any other stale entries encountered before the trailing null. See Knuth, Section 6.4

  • staleSlot : index of slot known to have null key
     private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}

若调用完 ThreadLocal 后,线程未结束,且没有调用 remove、set 和 get 方法。才会导致内存泄漏。虽然 get 和 set 时,会回收 value。但是我们不能依赖该机制回收内存,这只是个兜底的方案。开发中建议使用完 ThreadLocal 后就 remove 即可。

JDK8 ThreadLocal 源码解析与最佳实践相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 43 ThreadLocal 源码解析

    43 ThreadLocal 源码解析 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1 用法演示 ...

  2. 基于lis3dh的简易倾角仪c源码_开源网关apisix源码阅读和最佳实践

    大家应该都接手过这种项目,前人找一个开源软件改一改,发上线. 我这里便曾经遇到过类似的问题. 随着需求的增加,各种维护人员东改改西改改,原来的开源项目被改的面目全非,再也无法和上游合并. 甚至TLS协 ...

  3. 神兽大厅源码搭建SLB最佳实践

    一.SLB概念 负载均衡(Server Load Balancer)是将访问流量根据转发策略分发到后端多台云服务器(Elastic Compute Service,简称 ECS)的流量分发控制服务. ...

  4. 闲鱼源码页面SSR最佳实践

    背景 「让每一个用户在最短的时间内看到页面上重要的内容」一直以来都是前端工程师们精益求精的方向.对于一个H5的源码页面,我们已经有了很多缩短首屏渲染时间的方法,比如数据预取,离线缓存.但在目前看来,由 ...

  5. JDK8 HashMap源码解析

    1.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...

  6. ThreadLocal源码解析2.ThreadLocalMap

    1.ThreadLocalMap内部结构 static class ThreadLocalMap {/** 这里的Entry继承了弱引用,(弱引用只要发生GC就要被回收)* Entry的key(Thr ...

  7. Jdk8集合源码解析---ArrayList

    2019独角兽企业重金招聘Python工程师标准>>> ArrayList的数据结构 ArrayList是一种线性数据结构,底层是动态数组实现的,与java中的数组相比,它的容量可以 ...

  8. hashmap删除指定key_Java集合之HashMap源码解析(JDK8)

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景非常丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  9. 第42课: Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践

    第42课:  Spark Broadcast内幕解密:Broadcast运行机制彻底解密.Broadcast源码解析.Broadcast最佳实践 Broadcast在机器学习.图计算.构建日常的各种算 ...

最新文章

  1. 某程序员哀叹:二本计算机,4年开发,年包才40多万。二本真的不如985/211吗?...
  2. 做 AI 大咖在顶级单位之间随兴漂移,好开心!
  3. oracle数据库存储函数,Oracle数据库存储过程
  4. JDBC –模拟序列
  5. linux ubantu扩展空间,ubuntu 扩展存储空间
  6. 关于Java的静态初始化块
  7. Http网络传递参数中文乱码问题解决办法
  8. python_求1-2+3-4+......-100的值
  9. C3模块-空洞可分离卷积存在的问题及轻量化语义分割模型架构技巧
  10. Kernel同步机制的底层实现
  11. j1900做网站服务器,j1900可以跟云服务器
  12. vue简易微前端项目搭建(一):项目背景及简介
  13. 微信服务商-小程序支付-商户传入的appid参数不正确,请联系商户处理
  14. 75道程序员面试逻辑题和答案
  15. 大数据24小时:京东启动“人工智能加速器”项目,网传王劲因“家庭原因”离开景驰
  16. python中encode是什么意思_【转 记录】python中的encode以及decode
  17. 4、Proteus仿真STM32串口通信,发送数据控制LED流水灯的启动和停止
  18. 【经验】安全岗实习生应聘
  19. 阿里P8大牛用实例跟你讲明白“Java 微服务架构实战”
  20. 经常更换宽带账号时连接问题

热门文章

  1. C1. Pokémon Army (easy version)
  2. python降低图片分辨率怎么调_使用PIL调整图片分辨率
  3. Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
  4. 【AI选股】如何通过python调用通达信-小达实现AI选股(量化又多了一个选股工具)
  5. GUI用户图形界面:GUILayout控件及使用例子
  6. 移植AT91Bootstrap1.15
  7. C#编程基础——综合项目实践:KTV点歌系统项目第九课:制作歌曲排行窗体
  8. 通过身份证号码得到性别和出生日期
  9. Clock skew too great
  10. CGM植物微生物组专题研讨(王亚玉/王孝林/刘永鑫/周骏,晚7点)