【原理】Java的ThreadLocal实现原理浅读
当前线程的值传递,ThreadLocal
通过ThreadLocal设值,在线程内可获取,即时获取值时在其它Class或其它Method。
public class BasicUsage {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
threadLocal.set(1);
otherMethod();
}
public static void otherMethod() {
System.out.println("threadLocal.get() -> " + threadLocal.get()); // 其它Class、其它方法,只要在此线程内就可获取
}
}
结果:threadLocal.get() -> 1
如何设置值
这是设置值的方法,关键在于我们将值存放于ThreadLocalMap中
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap
if (map != null)
map.set(this, value); // 将值设置进ThreadLocalMap中
else
createMap(t, value); // 创建ThreadLocalMap
}
跟踪进去getMap(t),可知ThreadLocalMap是声明在Thread中的threadLocals变量中
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
跟踪进去createMap(t, value),可以看到实例化ThreadLocalMap的代码,我们留意到第一个参数是this,也就是ThreadLocal对象,下文会有提到:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
跟踪进ThreadLocalMap的构造方法,可以发现它内部维护一个Entry数组,构造方法的代码基本围绕将这个值应存放于数组的哪个下标。这里请注意下实例化Entry的参数,继续跟踪Entry:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // 初始化初始长度的Entry数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 与数组长度做与运算,得到存放的下标
table[i] = new Entry(firstKey, firstValue); // 实例化一个Entry对象
size = 1; // 记录当前的大小
setThreshold(INITIAL_CAPACITY); // 设置阀值,未以后扩容作计算依据
}
跟踪进Entry的构造方法,发现Entry继承WeakReference,以ThreadLocal作为Key,自己存储value:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */ // 此值与threadLocal关联
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
整体结构
- 值保持在
Thread中的变量threadLocals,此变量类型为ThreadLocal.ThreadLocalMap,可以看到,是ThreadLocal的内部类 ThreadLocal.ThreadLocalMap内部维护一个数组变量Entry[] table,这个数组类型是EntryEntry类型继承WeakReference<ThreadLocal<?>>,Entry会维护两个属性,分别是键和值,其中值由其自身维护,见Object value;键由WeakReference维护,可知如果不存在引用指向键,键对象可能被GC回收- 数据以
Entry的格式存储在ThreadLocal.ThreadLocalMap的变量Entry[] table中,其中位置这么计算int i = key.threadLocalHashCode & (len-1)

父线程、子线程中的值传递
JDK的InheritableThreadLocal
使用ThreadLocal中如果使用多线程,会发现父线程设置的值在子线程中无法获取,JDK中有InheritableThreadLocal解决此问题。
public class SubThreadUsage {
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
public static void main(String[] args) {
threadLocal.set(1);
// 新启一个线程
new Thread(new Runnable() {
@Override
public void run() {
otherMethod();
}
}).start();
}
public static void otherMethod() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
}
}
结果:threadLocal.get() -> 1
原理简述:
- 看
InheritableThreadLocal,会发现其继承ThreadLocal<T>,并且数据存放在Thread的变量inheritableThreadLocals中,变量类型是ThreadLocal.ThreadLocalMap - 在
Thread构造方法调用的init()中,可看见如果parent.inheritableThreadLocals不为空,则ThreadLocal.createInheritedMap()拷贝ThreadLocalMap,拷贝实际调用的是构造方法ThreadLocalMap(ThreadLocalMap),为浅拷贝
所以,如果运用线程池等线程复用技术,传递的数据会有遗留:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SubThreadReuseThreadUsage {
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
/* 声明多线程组件 */
ExecutorService executorService = Executors.newSingleThreadExecutor();
Runnable runnableA = new Runnable() {
@Override
public void run() {
otherMethodA();
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
otherMethodB();
}
};
// 运行一个线程
executorService.execute(runnableA);
TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完
/* 运行一个线程 */
executorService.execute(runnableB);
}
public static void otherMethodA() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
threadLocal.set(2);
}
public static void otherMethodB() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
}
}
结果:
threadLocal.get() -> 1
threadLocal.get() -> 2
TransmittableThreadLocal
而线程复用技术因减低线程开销而常用,所以需解决此问题,阿里开源的TransmittableThreadLocal是一个方案,其实现加强了InheritableThreadLocal。
用TransmittableThreadLocal、TtlRunnable的简单例子:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
public class SubThreadReuseThreadUsage {
private static ThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<Integer>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
/* 声明多线程组件 */
ExecutorService executorService = Executors.newSingleThreadExecutor();
Runnable runnableA = new Runnable() {
@Override
public void run() {
otherMethodA();
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
otherMethodB();
}
};
TtlRunnable ttlRunnableA = TtlRunnable.get(runnableA);
TtlRunnable ttlRunnableB = TtlRunnable.get(runnableB);
// 运行一个线程
executorService.execute(ttlRunnableA);
TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完
/* 运行一个线程 */
executorService.execute(ttlRunnableB);
}
public static void otherMethodA() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
threadLocal.set(2);
}
public static void otherMethodB() {
System.out.println("threadLocal.get() -> " + threadLocal.get());
}
}
结果:
threadLocal.get() -> 1
threadLocal.get() -> 1
这里通过使用TtlRunnable和TtlCallable完成,还可以通过使用TtlExecutors完成,另外还有无侵入方案Java Agent,详情见此。
【原理】Java的ThreadLocal实现原理浅读的更多相关文章
- Java集合&Spring源码浅读
记录自己现在知道的,以后了解了更多的话,再继续补上来 Java集合类 Collection 接口 说明:是List,set 的父类.定义了集合初始模样.集合只存储对象. Jdk8文档,内部方法定义有: ...
- Java中ThreadLocal无锁化线程封闭实现原理
虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLo ...
- Java多线程——ThreadLocal类的原理和使用
Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...
- java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用?
java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用? 说明类java.lang.ThreadLocal的作用和原理.列举在哪些程序中见过Threa ...
- [Java多线程]-ThreadLocal源码及原理的深入分析
ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- java的nio之:java的nio的原理
转载:http://weixiaolu.iteye.com/blog/1479656 Java NIO原理图文分析及代码实现 前言: 最近在分析hadoop的RPC(Remote Procedure ...
- Java NIO使用及原理分析 (四)
在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...
- Java位向量的实现原理与巧妙应用
Java位向量的巧实现原理与巧妙应用 1.博文介绍 本篇博文将会介绍几本的位运算含义.位向量介绍.BitSet实现原理.Java位向量的应用.拓展介绍Bloom Filter等. 2.位运算介绍 1) ...
随机推荐
- navigationItem.rightBarButtonItem 设置背景图片,颜色更改解决的方法
self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@& ...
- ios中网络请求缓存
#import <Foundation/Foundation.h> #import "ASIFormDataRequest.h" @protocol NetWorkde ...
- 内存问题的排查工具和方法– Clang的AddressSanitizer
1 概述 Valgrind可以有效地监测处大多数内存问题,你肯定忍不住会想,既然c/c++的内存问题这么常见,为什么不在编译器中加入内存问题检测的功能呢? 很可惜,GCC中还目前还不支持内存检测,可喜 ...
- 预加载与智能预加载(iOS)
来源:Draveness(@Draveness) 链接:http://www.jianshu.com/p/1519a5302141 前两次的分享分别介绍了 ASDK 对于渲染的优化以及 ASDK 中使 ...
- 【LeetCode】208. Implement Trie (Prefix Tree)
Implement Trie (Prefix Tree) Implement a trie with insert, search, and startsWith methods. Note:You ...
- 运维人员20道必会iptables面试题
1.详述iptales工作流程以及规则过滤顺序? iptables过滤的规则顺序是由上至下,若出现相同的匹配规则则遵循由上至下的顺序 2.iptables有几个表以及每个表有几个链? Iptables ...
- Redis学习之路(001)- Redis介绍以及安装(Linux)
redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcached类似,但很大程度补偿了memcached的不足,它支持存储的value类型相对更多,包括strin ...
- logstash向elasticsearch写入数据,如何指定多个数据template
之前在配置从logstash写数据到elasticsearch时,指定单个数据模板没有问题,但是在配置多个数据模板时候,总是不成功,后来找了很多资料,终于找到解决办法,就是要多加一个配置项: temp ...
- PHP中数字检测is_numeric与ctype_digit的区别介绍
PHP中的两个函数is_numeric和ctype_digit都是检测字符串是否是数字,但也存在一点区别 is_numeric:检测是否为数字字符串,可为负数和小数 ctype_digit:检测字符串 ...
- MongoDB学习笔记(11)-- Index
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录. 这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要 ...