ThreadLocal的原理图:

在线程任务Runnable中,使用一个及其以上ThreadLocal对象保存多个线程的一个及其以上私有值,即一个ThreadLocal对象可以保存多个线程一个私有值。

(重点)每一个线程Thread对象,都有一个threadLocals属性;

核心属性,因为每个Thread对象(线程)都拥有自己私有的threadLocals属性,ThreadLocal对象通过操作(set和get)每个线程自身私有属性threadLocals(数据结构:ThreadLocal.ThreadLocalMap)做到让每个线程拥有自己的私有变量;

threadLocals数据结构是ThreadLocal的内部类ThreadLocalMap,这个Map中的key是当前ThreadLocal对象,value是线程想要隔离的值,值的类型是ThreadLocal的泛型。

ThreadLocal源码实现

(1)set方法

ThreadLocal<String> local=new ThreadLocal<String>(); t.set("herock"); 为啥直接设置value就知道该value属于哪个线程?仔细体会上面的文字。

首先获取到当前线程,通过getMap(t)传入线程获得该线程持有的实例变量ThreadLocalMap。

getMap(t)返回线程t的实例变量threadLocals。

如果threadLocals为null就创建map,如果不为null以ThreadLocal实例为key,以所存值为value存入map;

而该Map是线程私有属性threadLocals变量的值,所以建议最好把ThreadLocal对象设置为static,这样该Runnable只创建一个ThreadLocal对象,每个相同或不同的线程任务都引用的同一个ThreadLocal对象;

什么叫相同或不同的线程任务?比如有RunnableA和RunnableB线程任务都需要ThreadLocal对象,一组线程执行A,一组线程执行B,他们都可以使用同一个ThreadLocal对象,即作为key存入线程自身的私有属性threadLocals中。

(2)ThreadLocal的get方法

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//这里的this是ThreadLocal对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//获得value
return result;
}
}
return setInitialValue();
}

防止内存泄漏

ThreadLocal 被线程的实例变量引用,在线程未死之前, ThreadLocal 变量无法被回收,会导致内存泄漏?其实不然,在源程序中已经有相应的措施了。

ThreadLocalMap 的每个 Entry 都是一个对  的弱引用.

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

ThreadLocalMap中的entry是弱引用(会在下一次垃圾回收时回收),从而避免上文所述 ThreadLocal 被引用不能被回收而造成的内存泄漏的问题。

但是,这里又可能出现另外一种内存泄漏的问题。

ThreadLocalMap 维护 ThreadLocal 变量与具体value的映射,当 ThreadLocal 变量被回收后,该Entry的key变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。

再看一次set方法

针对该问题,ThreadLocalMap 的 set 方法中:

首先,根据key(ThreadLocal对象)的hashcode与len-1进行按位与计算出在ThreadLocalMap中哈希数组的index下标值,tab[i]即为链表的头结点;

然后遍历以tab[i]的头结点链表中的元素,如果有元素等于了该Entry(弱引用的ThreadLocal),就把value付给该entry对应的value;

(重点)如果遍历的元素中有值为null(因为弱引用在垃圾回收时被回收了),就把该位置的元素的设置为null(通过 replaceStaleEntry 方法将所有key为 null 的 Entry 的值value设置为 null,从而使得该值可被回收,然后在把想要插入的key-value插入进去);

如果遍历完了都没有与待插入元素相等的链表节点,就将该键值对进行插入;

另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
    //这里是链表中某个Entry元素的key为null时,这里就要进行清理,然后再把想要插入的值key和value插入进去
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

总结

  • ThreadLocal 并不解决线程间共享数据的问题
  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap 的 Entry 为弱引用,避免了 ThreadLocal 对象无法被回收的问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景,比如一个Servlet类中有一个属性,有方法对该属性进行写操作和读操作,为了隔离

参考来源:

http://www.jasongj.com/java/threadlocal/

https://blog.csdn.net/herock3/article/details/80160978

ThreadLocal源码原理以及防止内存泄露的更多相关文章

  1. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

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

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

  3. ThreadLocal源码解读

    1. 背景 ThreadLocal源码解读,网上面早已经泛滥了,大多比较浅,甚至有的连基本原理都说的很有问题,包括百度搜索出来的第一篇高访问量博文,说ThreadLocal内部有个map,键为线程对象 ...

  4. 并发-ThreadLocal源码分析

    ThreadLocal源码分析 参考: http://www.cnblogs.com/dolphin0520/p/3920407.html https://www.cnblogs.com/coshah ...

  5. ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解

    本文脉路: 概念阐释 ---->  原理图解  ------> 源码分析 ------>  思路整理  ----> 其他补充. 一.概念阐述. ThreadLocal 是一个为 ...

  6. ThreadLocal源码分析-黄金分割数的使用

    前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对T ...

  7. ThreadLocal源码深度剖析

    ThreadLocal源码深度剖析 ThreadLocal的作用 ThreadLocal的作用是提供线程内的局部变量,说白了,就是在各线程内部创建一个变量的副本,相比于使用各种锁机制访问变量,Thre ...

  8. 硬核剖析ThreadLocal源码,面试官看了直呼内行

    工作面试中经常遇到ThreadLocal,但是很多同学并不了解ThreadLocal实现原理,到底为什么会发生内存泄漏也是一知半解?今天一灯带你深入剖析ThreadLocal源码,总结ThreadLo ...

  9. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

随机推荐

  1. VM无法正常使用桥接模式获取IP上网

    问题: 有时候会遇到VM使用桥接模式时无法正常获取IP的情况 原因: 初步怀疑是因为你的电脑是双网卡 解决方法: 这时候,就需要修改VM的虚拟网络编辑器的配置 解决步骤: 编辑->虚拟网络编辑器 ...

  2. WEBSHELL-恶意代码检测

    静态查杀 提取特征写成规则库,调用规则库查杀.基于规则,会比较快,但漏报.误报会比较明显,一般的Webshell一句话木马变形混淆会比较多. yara规则 $eval = /(<\?php|[; ...

  3. 解决org.springframework.web.multipart.MaxUploadSizeExceededException

    今天在spring boot2X 里做文件上传遇到了如下错误 org.springframework.web.multipart.MaxUploadSizeExceededException: Max ...

  4. Error: Error occured while starting App. Original error: Activity used to start app doesn't exist or cannot be launched! Make sure it exists and is a launchable activity

    Error: Error occured while starting App. Original error: Activity used to start app doesn't exist or ...

  5. 新唐NDA102EC1中更改UUART1作为调试串口打印输出调试信息

    新唐NDA102EC1中更改UUART1作为调试串口打印输出调试信息 参考网上STM32修改fputc(int ch, FILE *f),把串口打印函数printf()重定向到UUART1模块的端口输 ...

  6. 字符串A转换到字符串B,只能一次一次转换,每次转换只能把字符串A中的一个字符全部转换成另一个字符,是否能够转换成功

    public class DemoTest { public static void main(String[] args) { System.)); } /** * 有一个字符串A 有一个字符串B ...

  7. [转帖]技术人眼中的Facebook Libra

    技术人眼中的Facebook Libra https://www.jianshu.com/p/ddc733077749 比特币 以太坊 EOS Libra 0.8352019.07.01 14:15: ...

  8. Java 将 PPT 形状(表格、文本框、心形、图表等)保存成图片

    MS PowerPoint中的表格.文本框.心形.图表.图片等均可以称为形状,将这些形状保存成图片,便可分类储存,方便日后查找,再利用. 本文将介绍如何使用 Spire.Presentation fo ...

  9. AtCoder-abc147 (题解)

    A - Blackjack (水题) 题目链接 大致思路: 水题 B - Palindrome-philia (水题) 题目链接 大致思路: 由于整个串是回文串,只要判断前一半和后一半有多少个不同即可 ...

  10. 6. 运行Spark SQL CLI

    Spark SQL CLI可以很方便的在本地运行Hive元数据服务以及从命令行执行任务查询.需要注意的是,Spark SQL CLI不能与Thrift JDBC服务交互.在Spark目录下执行如下命令 ...