结构演进

早起JDK版本中,ThreadLocal内部结构是一个Map,线程为key,线程在“线程本地变量”中绑定的值为Value。每一个ThreadLocal实例拥有一个Map实例。(Key是线程,Value是值)

JDK8中,ThreadLocal内部结构发生了演进,虽然还是Map,但是拥有者变成了Thread实例,每一个Thread实例拥有一个Map实例。Map中的key变为ThreadLocal实例。(Key是ThreadLocal,Value是值)

新版ThreadLocalMap如图:

每一个线程在获取本地值时,都会将ThreadLocal实例作为Key从自己拥有的ThreadLocalMap中获取值,别的线程无法访问自己的ThreadLocalMap实例,达到相互隔离的目的。

ThreadLocal为什么会内存泄漏

我们知道,ThreadLocal是基于ThreadLocalMap实现的,这个Map的Entry继承了WeakReference,而Entry对象中的key使用了WeakReference封装,也就是说Entry中的key是一个弱引用类型,而弱引用类型只能存活在下次GC之前。如果一个线程调用ThreadLocal的set设置变量,当前ThreadLocalMap则新增一条记录,但发生一次垃圾回收,此时key值被回收,而value值依然存在内存中,由于当前线程一直存在,所以value值将一直被引用。这些被垃圾回收掉的key就存在一条引用链的关系一直存在:

Thread --> ThreadLocalMap -->Entry --> Value

这条引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

但是这些被动的预防措施并不能保证不会内存泄漏:

  • 使用线程池的时候,这个线程执行任务结束,ThreadLocal对象被回收了,线程放回线程池中不销毁,这个线程一直不被使用,导致内存泄漏。

  • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么这个期间就会发生内存泄漏。

我们只需要在使用完该key值之后,通过remove方法remove掉,就可以防止内存泄漏了。

Entry的Key使用弱引用

为什么不直接使用ThreadLocal实例作为Key呢?

key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get的时候会被清除。

比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下 一次ThreadLocalMap调用set,get,remove的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

弱引用:对象只能生存到下一次垃圾回收之前。

应用场景

线程隔离

这个的典型应用就是“数据库连接独享”。下面的代码来自Hibernate,代码通过ThreadLoacl进行数据库连接(Session)的线程本地化存储。

private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() {
Session s = (Session) threadSession.get();
if(s == null) {
s = getSession();
threadSession.set(s);
}
return s;
}

一个Session代表了一个数据库连接。通过以上代码可以看出,这个Session相当于线程的私有变量,不是所有线程共用的,其他线程是获取不到这个Session的。

一般来说,完成数据库操作之后程序会将Session关闭,节省资源。如果Session为共享的方式,如果某个线程将Session关闭,其他线程在操作Session时就会报错。所以通过ThreadLocal简单实现了数据库连接的安全使用。

Reference

《Java高并发核心编程》

https://www.cnblogs.com/arielmeng/p/15617405.html

ThreadLocal部分源码分析和应用场景的更多相关文章

  1. 并发编程(四):ThreadLocal从源码分析总结到内存泄漏

    一.目录      1.ThreadLocal是什么?有什么用?      2.ThreadLocal源码简要总结?      3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...

  2. 【源码分析】cocostudio场景编辑器的触发器逻辑

    去看场景编辑器的差不多都可以看到有模拟器的设置(菜单栏的设置).默认是选择cocostudio安装路径中的Simulator.exe这个模拟器,看官网介绍是自己可以选择模拟器,而且公开源代码可以按需设 ...

  3. ThreadLocal部分源码分析

    结构演进 早起JDK版本中,ThreadLocal内部结构是一个Map,线程为key,线程在"线程本地变量"中绑定的值为Value.每一个ThreadLocal实例拥有一个Map实 ...

  4. ThreadLocal和ThreadLocalMap源码分析

    目录 ThreadLocal和ThreadLocalMap源码分析 背景分析 定义 例子 源码分析 ThreadLocalMap源码分析 ThreadLocal源码分析 执行流程总结 源码分析总结 T ...

  5. 并发编程学习笔记(8)----ThreadLocal的使用及源码分析

    1. ThreadLocal的理解 ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境 ...

  6. ThreadLocal源码分析与实践

    ThreadLocal是什么? ThreadLocal是一个线程内部存储类,提供线程内部存储功能,在一个ThreadLocal对象中,每一个线程都存储各自独立的数据,互不干扰 示例如下: public ...

  7. lesson2:java阻塞队列的demo及源码分析

    本文向大家展示了java阻塞队列的使用场景.源码分析及特定场景下的使用方式.java的阻塞队列是jdk1.5之后在并发包中提供的一组队列,主要的使用场景是在需要使用生产者消费者模式时,用户不必再通过多 ...

  8. 02_ThreadLocal语法与源码分析

    文章导读: 早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程 ...

  9. Netty源码分析--内存模型(上)(十一)

    前两节我们分别看了FastThreadLocal和ThreadLocal的源码分析,并且在第八节的时候讲到了处理一个客户端的接入请求,一个客户端是接入进来的,是怎么注册到多路复用器上的.那么这一节我们 ...

  10. lesson1:threadlocal的使用demo及源码分析

    本文中所使用的demo源码地址:https://github.com/mantuliu/javaAdvance 其中的类Lesson1ThreadLocal 本文为java晋级系列的第一讲,后续会陆续 ...

随机推荐

  1. 使用端口排查解决启动Tomcat端口被占问题

    有时候在eclipse中启动Tomcat或启动纯净版的Tomcat会出现端口被占的问题,下面菜鸟小编带大家进行端口排查解决问题.(下面假设是我的80端口被占了,如果你不知道你的Tomcat端口是多少就 ...

  2. vmware workstation 16.x安装windows server 2012 R2无法点击安装vmware tools选项

    在使用vmware workstation预设安装windows server 2012 R2后,提示需要先升级KBxxxxx更新才可以安装vmware tools,但是在安装了所需的更新后,发现菜单 ...

  3. php对接java接口

    1.php curl 传参形式 public function send($url,$postData){ $ch = curl_init(); $headers = array("Cont ...

  4. Mixly智能门禁(物联网)

    智能门禁arduino rc522读卡器  sg90舵机 校园卡我的校园卡号识别为 30fcb4a8d #include <RFID.h>#include <SPI.h>#in ...

  5. 【Hive】数据倾斜原因及解决方法汇总

    1)数据倾斜根本原因:由于数据分布不均匀,导致map端读取的数据分布不均匀(数据长尾分布),从而使得map处理的数据量差异过大. (2)解决思路:Hive是分阶段执行的,map处理数据量的差异取决于上 ...

  6. nuxt中asyncData和fetch的区别

    asyncData作用于页面pages,在组件中不能使用,并且asyncData中没有this,如果想要给data中的数据赋值,要在asyncData函数中return出去 fetch 作用于组件中c ...

  7. 面向对象程序设计 第二章 C++简单的程序设计

    目录 C++语言的特点 1.兼容C语言 · 它保持了C的简洁.高效和接近汇编语言等特点. · 对C的类型系统进行了改革和扩充. · C++也支持面向过程的程序设计,不是一个纯正的面向对象的语言 2.支 ...

  8. md5信息摘要算法实现(python 和 go版本)

    纯手写实现的md5信息摘要算法 github地址如下 https://github.com/kittysmith5/dgst/blob/main/md5 python3版本代码 #!/usr/bin/ ...

  9. Spring系列之基于 Java 的容器配置-9

    目录 组合基于 Java 的配置 使用`@Import`注解 有条件地包含`@Configuration`类或`@Bean`方法 结合 Java 和 XML 配置 组合基于 Java 的配置 Spri ...

  10. Linux_MySQL

    MySQL 安装 AB复制 安装 1.编译安装 2.yum安装 [https://www.mysql.com/] yum安装的方式 1.在官网下载mysql rpm包 # wget https://d ...