JDK ThreadLocal解析
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方法的比较重要的部分源代码:
// 最开始计算的下标i
int 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 判断是否为当前的 ThreadLocal
if (k == key) {
e.value = value;
return;
}
// Entry 的 key 为空, 但是上面循环的判断是 Entry 不为空, 表明作为 Entry 的 key的 ThreadLocal 已经被 GC 回收, 所以此时设置新的 key(ThreadLocal) 和 value.
if (k == null) {
// 替换掉旧的 ThreadLocal
replaceStaleEntry(key, value, i);
return;
}
// 已经找到为空的 Entry, 直接 new 一个 Entry
tab[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 为应当属于它的 Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//
while (e != null) {
ThreadLocal<?> k = e.get();
// 查找到属于自己的 value
if (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 reference
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
// get
public 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 判断是否为当前的 ThreadLocal
if (k == key) {
e.value = value;
return;
}
// Entry 的 key 为空, 但是上面循环的判断是 Entry 不为空, 表明作为 Entry 的 key的 ThreadLocal 已经被 GC 回收, 所以此时设置新的 key(ThreadLocal) 和 value.
if (k == null) {
// 替换掉旧的 ThreadLocal
replaceStaleEntry(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)]) {
// 找到与自己匹配的 Entry
if (e.get() == key) {
e.clear();
// 这个方法名也说的很清楚了, 删掉陈旧的 Entry
expungeStaleEntry(i);
return;
}
}
}
所以使用 JDK 的 ThreadLocal , 在不需要的时候需要手动调用 remove() 方法, 防止导致内存泄漏, 最终导致 OOM.
最后
这次的内容到这里就结束了,最后的最后,非常感谢你们能看到这里!!你们的阅读都是对作者的一次肯定!!!
觉得文章有帮助的看官顺手点个赞再走呗(终于暴露了我就是来骗赞的(◒。◒)),你们的每个赞对作者来说都非常重要(异常真实),都是对作者写作的一次肯定(double)!!!
JDK ThreadLocal解析的更多相关文章
- 并发之线程封闭与ThreadLocal解析
并发之线程封闭与ThreadLocal解析 什么是线程封闭 实现一个好的并发并非易事,最好的并发代码就是尽量避免并发.而避免并发的最好办法就是线程封闭,那什么是线程封闭呢? 线程封闭(thread c ...
- JDK环境解析,安装和目的
目录 1. JDK环境解析 1.1 JVM 1.2 JRE 1.3 JDK 2. JDK安装 2.1 为什么使用JDK8 2.1.1 更新 2.1.2 稳定 2.1.3 需求 2.2 安装JDK 2. ...
- Java并发编程之ThreadLocal解析
本文讨论的是JDK 1.8中的ThreadLocal ThreadLocal概念 ThreadLocal多线程间并发访问变量的解决方案,为每个线程提供变量的副本,用空间换时间. ThreadLocal ...
- Java ThreadLocal解析
简介 ThreadLocal 类似局部变量,解决了单个线程维护自己线程内的变量值(存.取.删),让线程之间的数据进行隔离.(InheritableThreadLocal 特例) 这里涉及三个类,Thr ...
- ThreadLocal解析
ThreadLocal 如果定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap.并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在 ...
- 在Linux下安装JDK图文解析
参考自http://weixiaolu.iteye.com/blog/1401786 JDK官网下载:http://blog.csdn.net/chongxin1/article/details/70 ...
- Ubuntu下安装JDK图文解析
我们在64位的Ubuntu中安装JDK,选择的是jdk1.6.0_32版本号.安装文件名称为jdk-6u32-linux-x64.bin(这个是64位系统的),假设是32位系统的还须要去官网下载32位 ...
- ThreadLocal解析:父线程的本地变量不能传递到子线程详解
众所周知,ThreadLocal类是java提供线程本地变量的工具类.但父线程的本地变量却不能被子线程使用,代码如下: public static void main(String[] args) { ...
- Ubuntu下安装JDK以及相关配置
1.查看系统位数,输入以下命令即可 getconf LONG_BIT 2.下载对应的JDK文件,我这里下载的是jdk-8u60-linux-64.tar.gz 3.创建目录作为JDK的安装目录,这里选 ...
随机推荐
- 入门 - k8s伸缩应用程序 (六)
目标 使用 kubectl 伸缩应用程序. Scaling(伸缩)应用程序 在之前的文章中,我们创建了一个 Deployment,然后通过 服务 提供访问 Pod 的方式.我们发布的 Deployme ...
- winform 获得局域网内在线IP和计算机名,获取IP,多线程网络编程
转载请注明来源:https://www.cnblogs.com/hookjc/ using System; using System.Collections.Generic; using System ...
- UITableView的全部属性、方法以及代理方法执行顺序,看过之后肯定有收获---董鑫
UITableView-------表视图--继承UIScrollView并遵守NSCoding协议 属性 frame-------------设置控件的位置和大小 backgroundColor-- ...
- K8s多节点部署+负载均衡+keepalived ——囊萤映雪
K8s多节点部署+负载均衡+keepalived --囊萤映雪 1.多节点master2 部署 2.负载均衡部署+keepalived 1.多节点master2部署: #从master01节点上拷贝证 ...
- async同步异步
1.同步:var async = require("async"); async.series([step1, step2, step3],function(err, values ...
- 基于Oracle数据库登陆界面及功能实现 Java版
首先要在Oracle数据库创建表文件,包括建立表头以及关键字(唯一标识符),此次程序所用的表名称为SW_USER_INFO,表头有UNAME.UKEY.USEX等,关键字为UCC,然后添加一条记录,用 ...
- MySQL 快速入门(一)
目录 MySQL快速入门 简介 存储数据的演变过程 数据库分类 概念介绍 MySQL安装 MySQL命令初始 环境变量配置 MySQL环境变量配置 修改配置文件 设置新密码 忘记密码的情况 基本sql ...
- 7、Linux基础--权限、查看用户信息
笔记 1.晨考 1.Linux系统中的文件"身份证号"是什么 index node 号码 2.什么是硬链接,什么是软连接 硬链接是文件的入口,软连接是快捷方式. 3.硬链接中保存的 ...
- Solution -「ARC 104F」Visibility Sequence
\(\mathcal{Description}\) Link. 给定 \(\{x_n\}\),对于满足 \(h_i\in[1,x_i]\) 的序列 \(\{h_n\}\),定义序列 \(\{p ...
- 如何在TypeScript/JavaScript项目里引入MD5校验和
摘要:MD5校验和则是其中一种数学算法,通常是使用工具对文件计算得出的一组32 个字符的十六进制字母和数字. 本文分享自华为云社区<TypeScript/JavaScript项目里如何做MD5校 ...