并发之ThreadLocal
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set(T value):每次获取当前线程,从线程中获取其属性map,然后以当前threadLocal对象作为key,存放value;
get():每次获取当前线程,从线程中获取其属性map,然后以当前threadLocal对象作为key,获取value;
从上面的结构图,我们已经窥见ThreadLocal的核心机制:
- 每个Thread线程内部都有一个Map。
- Map里面存储线程本地对象(key)和线程的变量副本(value)
- 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
entry对象中存储了ThreadLocal对象的弱引用和这个ThreadLocal对应的value对象的强引用
当这个方法结束时,这个方法中创建的ThreadLocal对象本身(图中绿色区域)就被垃圾回收器回收了,但是线程还没有结束,
所以ThreadLocalMap中还存在这个entry。由于entry中的key(即ThreadLocal对象)是弱引用类型,
所以此时调用entry.get()方法时就会返回null,内部结构如下图所示。

从图中我们可以看到value对象(红色区域)始终不能被回收,而我们再也不会使用它了,这就造成了内存泄露。
那Entry中为什么保存的是key的弱引用呢?其实这是为了最大程度上减少内存泄露,副作用是同时减少哈希表中的冲突。
当ThreadLocal对象被回收时,对应entry中的key就自动变成null(entry对象本身不为null)。若此后我们调用get,set或remove方法时,
就会尝试删除key为null的entry,以释放value对象所占用的内存。
总结
- 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
- ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
- 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。
参考:
https://www.jianshu.com/p/98b68c97df9b
http://www.cnblogs.com/nullzx/p/7553538.html
并发之ThreadLocal的更多相关文章
- Android开发之ThreadLocal原理深入理解
[Android]ThreadLocal的定义和用途 ThreadLocal用于实现在不同的线程中存储线程私有数据的类.在多线程的环境中,当多个线程需要对某个变量进行频繁操作,同时各个线程间不需要同步 ...
- 并发之线程封闭与ThreadLocal解析
并发之线程封闭与ThreadLocal解析 什么是线程封闭 实现一个好的并发并非易事,最好的并发代码就是尽量避免并发.而避免并发的最好办法就是线程封闭,那什么是线程封闭呢? 线程封闭(thread c ...
- Java高并发之锁优化
本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 public synchronized void syncMethod(){ othercode1(); ...
- 高并发之Semaphore、Exchanger、LockSupport
本系列研究总结高并发下的几种同步锁的使用以及之间的区别,分别是:ReentrantLock.CountDownLatch.CyclicBarrier.Phaser.ReadWriteLock.Stam ...
- 微信公众号开发之VS远程调试
目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 前言 微信公众平台消息接口的工作原理大概可以这样理解:从用户端到公众号端一个流 ...
- ThreadLocal简单理解
在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...
- Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
前言: 为了加快开发效率,目前公司一些功能使用H5开发,这里难免会用到Js与Java函数互相调用的问题,这个Android是提供了原生支持的,不过存在安全隐患,今天我们来学习一种安全方式来满足Js与j ...
- Android混合开发之WebView与Javascript交互
前言: 最近公司的App为了加快开发效率选择了一部分功能采用H5开发,从目前市面的大部分App来讲,大致分成Native App.Web App.Hybrid App三种方式,个人觉得目前以Hybri ...
- Android线程管理之ThreadLocal理解及应用场景
前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...
随机推荐
- alloc_skb申请函数分析
alloc_skb()用于分配缓冲区的函数.由于"数据缓冲区"和"缓冲区的描述结构"(sk_buff结构)是两种不同的实体,这就意味着,在分配一个缓冲区时,需要 ...
- Android O HIDL的实现对接【转】
本文转载自:https://blog.csdn.net/gh201030460222/article/details/80551897 Android O HIDL的实现对接1. HIDL的定义1.1 ...
- vim插件的安装方式 -- vim注释插件和doxygen函数注释生成插件-ctrlp插件-tabular等号对齐 插件
使用unzip的时候 指定 -d选项, 是说明解压到的 目标地址. 这个参数还是比较方便的, 比直接unzip到当前目录, 然后在去拷贝到目标目录, 然后再删除当前目录中的解压文件夹, 方便多了. 使 ...
- 数论+矩阵快速幂|斐波那契|2014年蓝桥杯A组9-fishers
标题:斐波那契 斐波那契数列大家都非常熟悉.它的定义是: f(x) = 1 .... (x=1,2) f(x) = f(x-1) + f(x-2) .... (x>2) 对于给定的整数 n 和 ...
- 【C#】异步的用法
1. C#5.0 加入了async, await关键字. async是在声明异步方法时使用的修饰符, 声明放在返回值之前即可,await表达式则负责消费异步操作, 不能出现在catch或finally ...
- kubernets实战采坑1
1.NLog.config失效,日志ElasticSearch的Index不匹配 <?xml version="1.0" encoding="utf-8" ...
- Ubuntu 14.04 安装sublime
参考 How do I install Sublime Text 2/3? Ubuntu 14.04 安装sublime 通过apt-get包管理器安装sublime. sublime2.0: sud ...
- sed命令使用详解
内容来自马哥视频,感谢马哥精彩讲解 sed:编辑器 sed: Stream EDitor, 行编辑器,逐行进行处理 grep:实现文本过滤 awk:文本报告生成器 sed默认不编辑源文件,而是 ...
- 1、My Scripts
1.写一个包含命令.变量和流程控制的语句来清除/var/log的messages日志文件的shell脚本.(P26)(11-21) 2.利用$0和(dirname.basename)取出当前路径的目录 ...
- Spring Boot 发布方法 - 原创
发布方式 构建Jar包,cmd命令行运行Spring Boot程序 第一步:在pom.xml中将packing节点值修改为jar,如下面加粗部分: <groupId>com.example ...