Java ThreadLocal解析

ThreadLocal 线程本地变量, 线程私有, 在 Thread 类中用 ThreadLocal.ThreadLocalMap threadLocals 以数组的形式存储. 因为是线程私有的变量, 所以不会有多线程访问的线程安全问题, 下面就对它开始解析(JDK1.8 的 ThreadLocal).

threadLocalHashCode

在说 ThreadLocal 的使用之前, 先说一个比较重要的东西, 就是 threadLocalHashCode, 这个hashCode 的值用来计算 当前 ThreadLocal 应当位于 ThreadLocalMap 的数组中的下标, 为什么是应当呢, 请往下看

    // ThreadLocalHashCode 递增值private static final int HASH_INCREMENT = 0x61c88647;/*** Returns the next hash code.*/private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}// 每个 ThreadLocal 的 threadLocalHashCode 通过一个静态的原子变量递增而获得private final int threadLocalHashCode = nextHashCode();

ThreadLocal#set 方法

一个 ThreadLocalMap 可以存储多个 ThreadLocal 变量, 存取的时候将 ThreadLocal 作为key 放入 Entry 中, ThreadLocalMap 存储数据的对象为 Entry[], 计算 key 所属的下标用 int i = key.threadLocalHashCode & (len-1) 计算, 如果该下标已有数据, 根据 key(ThreadLocal变量) 判断是否属于这个 ThreadLocal, 不是同一个 ThreadLocal 则按顺序往后查找为空(或者 ThreadLocal 已经被GC回收)的 Entry设置 value.下面是 set方法的比较重要的部分源代码:

    // 最开始计算的下标iint i = key.threadLocalHashCode & (len-1);// 从计算出来的数组中的第 i 个开始往后找, i > len-1 的时候就从0开始for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {// nextIndex(i,len): ((i + 1 < len) ? i + 1 : 0); i+1>=len则从下标0开始ThreadLocal<?> k = e.get();// 根据 key 判断是否为当前的 ThreadLocalif (k == key) {e.value = value;return;}// Entry 的 key 为空, 但是上面循环的判断是 Entry 不为空, 表明作为 Entry 的 key的 ThreadLocal 已经被 GC 回收, 所以此时设置新的 key(ThreadLocal) 和 value.if (k == null) {// 替换掉旧的 ThreadLocalreplaceStaleEntry(key, value, i);return;}// 已经找到为空的 Entry, 直接 new 一个 Entrytab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)// 扩容将所有的 Entry 重新设置rehash();}

ThreadLocal#get 方法

get 方法跟 set 方法还是有挺多相同处的, 看代码

    private Entry getEntry(ThreadLocal<?> key) {// 计算下标int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];// 如果对应下标的 Entry 属于当前 ThreadLocal 的话, 直接返回结果if (e != null && e.get() == key)return e;else// 会到这里的原因是因为 set 的时候的 hash 冲突, 冲突后会往后查找(超过len-1则从0开始)可以放置的位置, 所以前面初始化 hashCode 的时候是用的应当位于的位置return getEntryAfterMiss(key, i, e);}// e 为应当属于它的 Entryprivate Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;// while (e != null) {ThreadLocal<?> k = e.get();// 查找到属于自己的 valueif (k == key)return e;// 如果在查找的时候发现有的 Entry 的 key(ThreadLocal) 已经被 GC 回收, 则进行回收if (k == null)expungeStaleEntry(i);else// 往后查找, 超过 len-1 则从 0 开始继续i = nextIndex(i, len);e = tab[i];}return null;}

关于ThreadLocal 的 WeakReference

在说 ThreadLocal 的 WeakReference 之前, 先简单介绍一下 WeakReference .

WeakReference

弱引用不同于 java 中用 new创建(强引用) 的对象, 当对象只有弱引用(weakReference) 的时候, 进行GC的时候, 会对其进行回收(没有强引用的对象均会被gc回收)

    Object o = new Object();WeakReference weakReference = new WeakReference(o);System.out.println("beforeGC: "+weakReference.get());o = null;System.gc();System.out.println("afterGC: "+weakReference.get());Object o2 = new Object();WeakReference weakReference2 = new WeakReference(o2);System.out.println("beforeGC2: "+weakReference2.get());System.gc();System.out.println("afterGC2: "+weakReference2.get());

输出结果是:

beforeGC: java.lang.Object@5e2de80c
afterGC: null
beforeGC: java.lang.Object@1d44bcfa
afterGC: java.lang.Object@1d44bcfa

从这里可以看出, 即使有 weakFerence 弱引用, 也不能阻止对象被 GC 回收掉(因为它很弱啊哈哈).

ThreadLocal 中使用的 WeakReference

从上面set的方法看到了一行 tab[i] = new Entry(key, value)

Entry 是一个静态类

    // Entry 继承 WeakReference, 多了个 value 记录 ThreadLocal 的值static class Entry extends WeakReference<ThreadLocal<?>> {// 这个值就是 ThreadLocal set 进来的值Object value;Entry(ThreadLocal<?> k, Object v) {// 将 ThreadLocal 设置为引用对象super(k);value = v;}}

super(k)就是将 ThreadLocal 设置为引用对象, 获取的时候,调用父类的get方法, entry.get() --> 返回的就是引用的对象(这里就是 ThreadLocal)

    // super(k)Reference(T referent) {this(referent, null);}// set referenceReference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}// getpublic T get() {return this.referent;}

代码看起来难度不大, 下面说一下 JDK 的 ThreadLocal 使用不当会导致内存泄漏的问题.

在上面的 set 和 get 方法中, 有这么(类似)一段代码:

    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {// nextIndex(i,len): ((i + 1 < len) ? i + 1 : 0); i+1>=len则从下标0开始ThreadLocal<?> k = e.get();// 根据 key 判断是否为当前的 ThreadLocalif (k == key) {e.value = value;return;}// Entry 的 key 为空, 但是上面循环的判断是 Entry 不为空, 表明作为 Entry 的 key的 ThreadLocal 已经被 GC 回收, 所以此时设置新的 key(ThreadLocal) 和 value.if (k == null) {// 替换掉旧的 ThreadLocalreplaceStaleEntry(key, value, i);return;}

if(k == null) 这里, 加上外层循环就是 if(e!=null && k == null), Entry不为空表明之前设置过 ThreadLocal 的值, 但是 k(key)==null 也就是 ThreadLocal == null, 为什么ThreadLocal 会为空? 前面我们说到 Entry 里面对 ThreadLocal(key) 是使用的 弱引用 , 也就是 Entry 持有 ThreadLocal 的引用并不会阻止GC 将 ThreadLocal 回收掉, 如果 ThreadLocal 手动置为 null , 则失去了强引用, 若其他地方没有这个 ThreadLocal 的强引用, 这个ThreadLocal 将会被GC回收掉, 此时 Entry 里面的 key == null, 但是 Entry 对 value 是强引用, 所以 value 不会被GC回收掉, 此时就造成了 Entry 的 key==null 但是 value !=null, 如果没有其它的 ThreadLocal 被设置进(或者get)同一个 Entry 的话, 这个 Entry 将会一直持有无用的 value, 造成内存泄漏, 泄漏的过多最终会导致 OOM.

解决办法呢就是在 ThreadLocal 置为 null之前, 先调用 remove 方法:

    private void remove(ThreadLocal<?> key) {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)]) {// 找到与自己匹配的 Entryif (e.get() == key) {e.clear();// 这个方法名也说的很清楚了, 删掉陈旧的 EntryexpungeStaleEntry(i);return;}}}

所以使用 JDK 的 ThreadLocal , 在不需要的时候需要手动调用 remove() 方法, 防止导致内存泄漏, 最终导致 OOM.

转载于:https://www.cnblogs.com/wuhaonan/p/11427119.html

JDK ThreadLocal解析相关推荐

  1. 并发之线程封闭与ThreadLocal解析

    并发之线程封闭与ThreadLocal解析 什么是线程封闭 实现一个好的并发并非易事,最好的并发代码就是尽量避免并发.而避免并发的最好办法就是线程封闭,那什么是线程封闭呢? 线程封闭(thread c ...

  2. 超详细在Ubuntu下安装JDK图文解析

    我们选择的是jdk1.6.0_30版本.安装文件名为jdk-6u30-linux-i586.bin. 1.复制jdk到安装目录 (1)假设jdk安装文件在桌面,我们指定的安装目录是:/usr/loca ...

  3. 动态代理[JDK]机制解析

    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 动态代理是一种比较常用的代理 ...

  4. 吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

    欢迎关注方志朋的博客,回复"666"获面试宝典 1 FastThreadLocal的引入背景和原理简介 既然jdk已经有ThreadLocal,为何netty还要自己造个FastT ...

  5. 吊打 ThreadLocal!

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://blog.csdn.net/mycs2012/article/details/90898128 FastThr ...

  6. threadlocal存连接对象的目的_终于懂了ThreadLocal,不再害怕面试官问了

    ThreadLocal解析 synchronized和ThreadLocal的区别: synchronized:以时间换空间,只提供一份变量,让不同的线程排队访问,失去了并发性,降低了程序效率,着重对 ...

  7. eclipse包含jdk吗_eclipse安装教程jdk

    JDK是解析器,用来解析CLASS文件的.而ECLIPSE是用JAVA编写的集成开发系统.它有很多插件支持可以编写C/C++.JSP.XML.UML,是很强大的开发工具. . 你如果想运行JAVA文件 ...

  8. 这些Java8官方挖的坑,你踩过几个?

    导读:系统启动异常日志竟然被JDK吞噬无法定位?同样的加密方法,竟然出现部分数据解密失败?往List里面添加数据竟然提示不支持?日期明明间隔1年却输出1天,难不成这是天上人间?1582年神秘消失的10 ...

  9. python官网下载步骤linux-linux如何安装python

    Linux中如何安装Python? 1.安装pycharm Pycharm下载地址:http://www.jetbrains.com/pycharm/download/ (建议选择开源的社区版本) 2 ...

  10. Java里的线程控制

    这篇文章接着上篇文章<<java 线程简介>> 写的. http://blog.csdn.net/nvd11/article/details/19118683 上一篇文章提到, ...

最新文章

  1. 538. Convert BST to Greater Tree 把二叉搜索树转换为累加树
  2. 链表逆序(JAVA实现)
  3. jQuery UI =jquery-ui.js中sortable方法拖拽对象位置偏移问题
  4. 流式计算之Storm简介
  5. 白群晖图文版阿里云域名解析IPV6远程访问方法
  6. win10计算机的用户名和密码在哪里查,Win10查看别人在自己电脑上输入过的账号密码...
  7. 工控机上位机软件的开发历程(一)
  8. android qq 目录,手机qq存储路径在哪可以找到
  9. Beta版本 为什么程序员总是分不清万圣节和圣诞节?因为 Oct 31 == Dec 25。
  10. 51单片机串口通信(自动发送)
  11. C语言--第二篇类型、运算符与表达式
  12. Vue element 自定义表单验证(验证联系方式、邮箱、邮政编码)
  13. 小米的逆转密码:MIX2与新零售
  14. AGC001 E BBQ Hard
  15. Kafka快速入门(Kafka消费者)
  16. 博客网页日志页面(HTML+CSS)
  17. 【实用技巧】CCS.V8创建TMS320F2812工程
  18. 汇总:Linux下文件操作接口
  19. 如何选择U盘加密软件?
  20. 关于利用qrcode生成二维码的两种方式的区别

热门文章

  1. OpenCV-图像处理(24、直方图计算)
  2. C程序设计--查找(二分法查找/折半查找)
  3. 多项logistic回归系数解释_逻辑回归logistic(含python代码)
  4. ckc交易什么意思_在期货交易中,所谓的期货对冲是什么意思?
  5. 链表 之 字典树(讲解+模板)的构建
  6. 线性代数中矩阵相乘如何计算
  7. C++中的set(STL的应用部分)
  8. 【数据结构笔记】哈夫曼树的构造算法
  9. openstack配置mysql_Centos7.4安装openstack(queens)详细安装部署(三)-镜像服务(glance)安装...
  10. log4net配置mysql_使用独立的log4net.config文件配置log4net,将日志记录到Mysql数据库【原创】...