ThreadLocal 线程主变量
前面部分引用其他优秀博客,后面源码自己分析的,如有冒犯请私聊我。
用Java语言开发的同学对 ThreadLocal 应该都不会陌生,这个类的使用场景很多,特别是在一些框架中经常用到,比如数据库事务操作,还有MVC框架中数据跨层传递。这里我们简要探讨下 ThreadLocal 的内部实现及可能存在的问题。
首先问自己一个问题,让自己实现一个这个的功能类的话怎么去做?第一反应就是简单构造一个 Map<Thread, T> 数据结构,key是 Thread,value就是我们要保存的线程变量 T。我们看下这种设计有哪些问题:
  • 随着运行时间越久,存在Map里的Thread越多,当Thread退出时,资源也没有释放,存在内存泄漏问题
  • Map数据因为会被多线程访问,存在资源竞争,所以还必需对Map做同步安全操作,效率低下
JDK中的 ThreadLocal 精妙的设计来解决问题上述两个问题。首先每个Thread(线程)内部都有一个Map结构数据ThreadLocalMap<ThreadLocal, T>,当我们对线程变量赋值时ThreadLocal.set(T value)时,其实是先获取当前线程Thread.currentThread())的内部属性字段ThreadLocalMap,然后以当前ThreadLocal为key设置线程变量值T。这种设计的精髓是,每个Thread线程都维护一份自己的ThreadLocalMap数据结构,这样就解决了上面所述问题中的第二个,不存在竞争条件。
既然每个Thread内部都维护一个ThreadLocalMap字典数据结构,字典的Key值是ThreadLocal,那么当某个ThreadLocal对象不再使用(没有其它地方再引用)时,每个已经关联了此ThreadLocal的线程怎么在其内部的ThreadLocalMap里做清除此资源呢?JDK中的ThreadLocalMap又做了一次精彩的表演,它没有继承java.util.Map类,而是自己实现了一套专门用来定时清理无效资源的字典结构。其内部存储实体结构Entry<ThreadLocal, T>继承自java.lan.ref.WeakReference,这样当ThreadLocal不再被引用时,因为弱引用机制原因,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap会释放其对ThreadLocal的引用从而让jvm回收ThreadLocal对象。这里是重点强调下,是回收对ThreadLocal对象,而非整个Entry,所以线程变量中的值T对象还是在内存中存在的,所以内存泄漏的问题还没有完全解决。接着分析JDK的实现,会发现在调用ThreadLocal.get()或者ThreadLocal.set(T)时都会定期执行回收无效的Entry操作。所以这就解决了上述问题中的第1个问题。
问题真的都解决了吗,好像都解决了。因为即没有竞争资源操作,也不会存在内存泄漏。但是细想一下,总感觉哪里不对劲,真的不会存在内存溢出(OOM)问题吗?上面一段的分析中,强调ThreadLocalMap会定期清理内部的无效Entry对象,触发的条件就是对TrheadLocal执行 set,get,remove()等操作时会触发,但是如果存在这样的场景,当我们在某个线程上下文中执行ThreadLocal.set(T)设置了一个很大内存的数据结构,然后该ThreadLocal被清除引用回收,之前的线程又一直存活着,则这个大内存数据对象T是一直不回收的,这里很容易写个代码测试出OOM来。怎么解决这个问题呢?
Lucene中的org.apache.lucene.util.CloseableThreadLocal类解决了上述特殊场景引起的问题:即解决JDK中因为定期才执行无效对象回收的问题。CloseableThreadLocal在内部维护了一个ThreadLocal,当执行CloseableThreadLocal.set(T)时,内部其实只是代理的把值赋给内部的ThreadLocal对象,即执行ThreadLocal.set(new WeakReference(T))。看到这里应该明白了,这里不是直接存储T,则是包装成弱引用对象,目的就是当内存不足时,jvm可以回收此对象。但是细心的你会发现会引入一个新的问题,即当前线程还存活着的时候,因为内存不足而回收了弱引用对象,这样会在下次调用get()时取不到值返回null,这是不可接受的。所以CloseableThreadLocal在内部还创建了一个数据,WeakHashMap<Thread, T>,当线程只要存活时,则T就至少有一个引用存在,所以不会被提前回收。但是又引入的第2个问题,对WeakHashMap的操作要做同步synchronized限制。你看,所有的东西都不是十全十美的,我们掌握那个平衡点就行了。
ThreadLocal源码分析
 
源码介绍
1、每个线程访问自己维护的的一份副本
2、ThreadLocal 类内部维护了一个私有的字段去跟对应的线程联系(例如 userid 或者TranslationId) 这个字段第一次调用时生成后面调用不会改变

3、只要这个线程活着而且实例可以被访问,这个线程会持有这个变量副本的隐性引用,直到线程消亡,被垃圾回收

 
ThreadLocal get方法
1、根据当前thread获取对应的ThreadLocalMap 如果没有就初始化设置一个,如果有就返回 ThreadLocalMap 里面维护的Entry存储的值
 public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
 
ThreadLocal 初始化value
俩个步骤 1、在原有的map上设置值 2、创建一个ThreadLocalMap
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
 
ThreadLocal rehash 1、先删除陈旧的Entriy ,如果不能有效的收缩table的长度,而且长度已经大于threshold 的0.75(装载因子)倍了,就直接扩展一倍长度
 /**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}

如果你也有此类问题,可以一起探讨(私聊或者评论),一起不断完善自己的理解,如果觉得可以欢迎关注我。

 
 

ThreadLocal介绍以及源码分析的更多相关文章

  1. ThreadLocal和ThreadLocalMap源码分析

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

  2. ThreadLocal 简介 案例 源码分析 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. ArrayList相关方法介绍及源码分析

    目录 ArrayList简介: ArrayList 相关方法介绍 代码表示 相关方法源码分析 ArrayList简介: java.util.ArrayList 是我们最常用的一个类,ArrayList ...

  4. Redis 专栏(使用介绍、源码分析、常见问题...)

    一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...

  5. ThreadLocal与ThreadLocalMap源码分析

    ThreadLocal类 该类主要用于不同线程存储自己的线程本地变量.本文先通过一个示例简单介绍该类的使用方法,然后从ThreadLocal类的初始化.存储结构.增删数据和hash值计算等几个方面,分 ...

  6. Spring AOP介绍及源码分析

    转自:http://www.uml.org.cn/j2ee/201301102.asp 软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程:前者是为了提高开发效率,而后者则使用了归纳法,把 ...

  7. Spring Security(3):配置与自动配置的介绍及源码分析

    基于注解的配置(Java Configuration)从Spring Security 3.2开始就已经支持,本篇基于Spring boot注解的配置进行讲解,如果需要基于XML配置(Security ...

  8. ThreadLocal应用及源码分析

    ThreadLocal 基本使用 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传 ...

  9. Spring Security(1):认证和授权的核心组件介绍及源码分析

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方式的安全框架.它包括认证(Authentication)和授权(Authorization)两个部 ...

随机推荐

  1. day12学python 多进程+进程池

    多进程+进程池 多进程(不同进程不可直接访问数据) 引入(多进程套线程) 多进程 需导入multiprocessing模块 模板示例1 import threading,time,multiproce ...

  2. python 导入模块与使用

    学习python之前一定要了解导入模块是怎么导入的,至少在看代码时不知道能很快的了解别人写的东西是哪里来. Python默认仅安装部分基础或核心模块,启动时也仅加载了基础模块,在需要是再显式的加载其他 ...

  3. View-Controller-Containment

    willMove(toParentViewController:) 调用时机 调用addChildViewController(_:)以钱会被自动调用 调用removeFromParentViewCo ...

  4. unity里的c#

    类方法:start只进行一次,update每一帧都会进行调用 拖动脚步到对应物体就可以将其关联 定义变量与c++相似 F2修改文件名 脚本的类名要与文件名保持一致 数组的定义:1.类型[] 数组名={ ...

  5. PHP开发微信公众号(二)消息接受与推送

    上一篇文章我们知道怎么获取二维码,这样别人就可以扫描二维码来关注我们,但是别人关注后,发送消息,我们怎么进行相关处理? 这里我们就来学习下怎么处理处理这些消息,以及推送消息. 学习之前首先你需要有一个 ...

  6. 方法引用(Method reference)和invokedynamic指令详细分析

    方法引用(Method reference)和invokedynamic指令详细分析 invokedynamic是jvm指令集里面最复杂的一条.本文将详细分析invokedynamic指令是如何实现方 ...

  7. 大话AJAX原理

    大话AJAX原理 一.什么是Ajax Ajax(Asynchronous JavaScript and XML的缩写)是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助.简单地说 ...

  8. CentOS 7 查看和设置防火墙状态

    CentOS7 默认使用的是firewall作为防火墙 查看防火墙状态 firewall-cmd --state 停止firewall systemctl stop firewalld.service ...

  9. 【笔记】Django的视图

    [笔记]Django的视图 Python Django  Django的View(视图) 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应. 响应 ...

  10. Python3 scrapy 新手命令

    基本命令 建立项目 scrapy startproject projectname #在CMD命令框内执行,路径是你需要保存的位置 建立爬虫 cd projectname #在CMD命令框内执行,目的 ...