一、引言

ThreadLocal是Java帮助实现线程封闭性的典型手段。

作用:提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量的传递复杂度。同时也用来维护线程中的变量不被其他线程干扰。

这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get 与set方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值

二、ThreadLocal的简单应用

ThreadLocal是使用空间换时间,synchronized是使用时间换空间,比如在hibernate中session就存在于ThreadLocal中,避免synchronized的使用。

下面程序的输出结果为null,因为从ThreadLocal中取出的对象一定是本线程中set的对象,别的线程无法取出,因为线程自己放入的对象只能自己取得,因此无需进行加锁处理,执行效率上ThreadLocal比synchronized要高。

public class ThreadLocal_02 {static ThreadLocal<Person> tl = new ThreadLocal<>();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}System.out.println(tl.get()); // output : null}).start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}tl.set(new Person("张三"));}).start();}static class Person {String name;public Person(String name) {this.name = name;}}
}

三、对ThreadLocal的理解

ThreadLocal对象通常用于防止对可变的单例变量或全局变量进行共享。

例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接:

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){public Connection initialValue() {return DriverManager.getConnection(DB_URL);}};public static Connection getConnection() {return connectionHolder.get();}

在比如,当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用ThreadLocal。

四、ThreadLocal的实现原理

ThreadLocal内部提供了四个对外开放的接口方法,这也是用户操作ThreadLocal对象的基本方法:

1、public T get() :取得线程局部变量

2、public void set(T value) :设置线程局部变量

3、public void remove() :删除线程局部变量

4、protected T initialValue() :返回该线程局部变量初始值

思考:ThreadLocal的实例是如何为每一个线程维护变量副本的呢?

上图来自http://www.importnew.com/22039.html

其实,每一个线程Thread其内部都维护一个ThreadLocal.ThreadLocalMap的实例对象(变量名为:threadLocals)。

你可以将这个ThreadLocalMap对象理解为一个Map,但实际上它是一个数组,一个以封装了ThreadLocal为键,Object为值的元素的数组。也就是说ThreadLocal本身不存储值,它只是作为一个key来让当前线程从ThreadLocalMap中获取value。值得注意的是,ThreadLocalMap是使用 ThreadLocal的弱引用作为 Key 的,弱引用的对象在GC时会被回收。

static class ThreadLocalMap {//map中的每个节点Entry,其键key是ThreadLocal并且还是弱引用static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}// 初始化容量为16,以为对其扩充也必须是2的指数private static final int INITIAL_CAPACITY = 16;// 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entryprivate Entry[] table;///....其他方法和操作都和map类似
}

由此,我们可以大概了解到了其线程局部变量的维护机制:为不同的线程创建不同的ThreadLocalMap,以线程本身作为区分,每个线程之间没有任何联系。

下面感兴趣可以看一下get()、set()的源码:

public T get() {Thread t = Thread.currentThread();//当前线程ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMapif (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//获取对应ThreadLocal的变量值if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//若当前线程还未创建ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行创建并返回初始值。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);
}

五、ThreadLocal内存泄漏问题

5.1 ThreadLocal为什么会内存泄漏?

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束(如线程池的线程回收)的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏

其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

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

5.2 为什么使用弱引用?

从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

我们先来看看官方文档的说法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

下面我们分两种情况讨论:

  • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

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

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

5.3 有效避免内存泄漏的最佳实践

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

六、鸣谢

《深入剖析ThreadLocal实现原理以及内存泄漏问题》

《深入分析 ThreadLocal 内存泄漏问题》

Java 多线程 —— ThreadLocal相关推荐

  1. Java多线程——ThreadLocal

    如果需要在多个线程之间共享资源,达到线程之间的通信可以使用同步机制:如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal. public class ThreadLocal<T&g ...

  2. Java多线程中的ThreadLocal,可继承,可修改

    Java多线程中的ThreadLocal,可继承,可修改. package test;import java.util.Date;public class InheritableThreadLocal ...

  3. (Java多线程常见面试题)ThreadLocal 是什么?有哪些使⽤场景?

    最近在研究多线程项目时,无意间看到一个很有意思的Java类----ThreadLocal.于是乎一向对于新东西充满好奇的我又开始了一系列深挖细究,在经过学习和参考网上其他大佬的见解后,现将自己的理解作 ...

  4. java多线程之线程本地数据ThreadLocal

    layout: post title: "java多线程之线程本地数据ThreadLocal" subtitle: " "每个线程都有自己的数据,互不干扰.&q ...

  5. 40个Java多线程问题总结

    (转) 这篇文章作者写的真是不错 40个问题汇总 1.多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓"知其然知其所 ...

  6. Java多线程常见面试题及答案汇总1000道(春招+秋招+社招)

    Java多线程面试题以及答案整理[最新版]Java多线程高级面试题大全(2021版),发现网上很多Java多线程面试题都没有答案,所以花了很长时间搜集,本套Java多线程面试题大全,汇总了大量经典的J ...

  7. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

  8. JAVA多线程和并发基础面试问答

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在 ...

  9. Java多线程发展简史

    摘自: http://www.raychase.net/698 这篇文章,大部分内容,是周五我做的一个关于如何进行Java多线程编程的Knowledge Sharing的一个整理,我希望能对Java从 ...

最新文章

  1. python目录大纲
  2. 013_SpringBoot视图层技术thymeleaf-迭代遍历
  3. FORMS变量类型和消息提示
  4. SAP S/4HANA Smart Business Filter is too complex - step by step
  5. ES8新特性_ES8中对象方法的扩展---JavaScript_ECMAScript_ES6-ES11新特性工作笔记052
  6. entity framework 数据库默认时间的问题的一种解决方案
  7. 工具-管理工具资源集合
  8. C语言快速找答案,C语言单选题找答案
  9. c语言入门介绍 Hello, World
  10. java 规范 阿里巴巴_阿里巴巴 Java 代码规范
  11. 高级研发工程师都有哪些特点?快来看看你是否符合【超级准】
  12. 使用Safari只要打开echarts图表的网址会使Safari未响应
  13. 论文阅读——Aspect Sentiment Quad Prediction as Paraphrase Generation
  14. FinalShell Mac OS版安装
  15. 空指针带来的AV异常.
  16. 手机中的照片不见了如何恢复
  17. 小程序之任务发布与接单平台
  18. 低学历者该如何学习计算机技术
  19. 【计算机原理与接口技术(UNIX)⑲ 完结篇】——可编程计数器 8254 [ 流光发生器、8254工作方式检测程序的设计]
  20. 效应论——破窗效应(zt)

热门文章

  1. 优雅统计代码耗时的4种方法!
  2. python调用cv2.findContours时报错:ValueError: not enough values to unpack (expected 3, got 2)
  3. python小孩的报酬_孩子做了家务,家长该不该支付“酬劳”呢?
  4. imx6 android快速启动,android启动不起来(已解决)
  5. 怎么把项目的数据上传到服务器,怎么把sql数据库上传到云服务器
  6. php fuzzy,模糊C均值聚类算法(Fuzzy C-means)
  7. java dispatcher详解_dispatcherservlet初始化过程详解
  8. php 查文件sha1 内存不足,SHA是否足以检查文件重复? (PHP中的sha1_file)
  9. esc指令检查打印状态_【行业知识分享】八千字解读ESC系统
  10. mysql multi主从复制_mysqld_multi方式配置Mysql数据库主从复制