目录(jdk1.8)
- 一、什么是threadlocal
- 二、threadlocal怎么用
- 三、threadlocal的原理
- 四、threadlocal源码分析
- 1.threadlocal的内部属性
- 2.threadlocal 之 set() 方法
- 3.threadlocal 之 get() 方法
- 4.treadlocal的remove方法
- 5.内部类threadlocalmap的基本结构和源码分析
-
- 5.1先看成员和结构部分
-
- 5.2接着看threadlocalmap的构造函数
-
- 5.3threadlocalmap 之 set() 方法
-
- 5.4threadlocalmap 之 getentry() 方法
-
- 5.5threadlocalmap 之 rehash() 方法
-
- 5.6threadlocalmap 之 remove(key) 方法
- 五、什么情况下threadlocal的使用会导致内存泄漏
- 六、threadlocal的最佳实践
- 七、黄金分割 - 魔数0x61c88647
- 八、总结
threadlocal 是 jdk java.lang 包下的一个类,是天然的线程安全的类,
1.threadloca 是线程局部变量,这个变量与普通变量的区别,在于每个访问该变量的线程,在线程内部都会
初始化一个独立的变量副本,只有该线程可以访问【get() or set()】该变量,threadlocal实例通常声明
为 private static。
2.线程在存活并且threadlocal实例可被访问时,每个线程隐含持有一个线程局部变量副本,当线程生命周期
结束时,threadlocal的实例的副本跟着线程一起消失,被gc垃圾回收(除非存在对这些副本的其他引用)
jdk 源码中解析:
/**
* this class provides thread-local variables. these variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code threadlocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user id or transaction id).
* /
稍微翻译一下:threadlocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问threadlocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。threadlocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户id或事务id)与线程关联起来。
讨论threadlocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈threadlocal的,threadlocal是用在多线程的场景的!!!
threadlocal归纳下来就3类用途:
- 保存线程上下文信息,在任意需要的地方可以获取!!!
- 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
- 线程间数据隔离
1.保存线程上下文信息,在任意需要的地方可以获取!!!
由于threadlocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
常用的比如每个请求怎么把一串后续关联起来,就可以用threadlocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。
还有比如spring的事务管理,用threadlocal存储connection,从而各个dao可以获取同一connection,可以进行事务回滚,提交等操作。
2.线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!
threadlocal局限性
threadlocal为解决多线程程序的并发问题提供了一种新的思路。但是threadlocal也有局限性,我们来看看阿里规范:
这类场景阿里规范里面也提到了:
threadlocal用法
public class mythreadlocaldemo {
private static threadlocal threadlocal = new threadlocal();
public static void main(string[] args) throws interruptedexception {
int threads = 9;
mythreadlocaldemo demo = new mythreadlocaldemo();
countdownlatch countdownlatch = new countdownlatch(threads);
for (int i = 0; i < threads; i ) {
thread thread = new thread(() -> {
threadlocal.set(thread.currentthread().getname());
system.out.println("threadlocal.get()================>" threadlocal.get());
countdownlatch.countdown();
}, "执行线程 - " i);
thread.start();
}
countdownlatch.await();
}
}
代码运行结果:
threadlocal.get()================>执行线程 - 1
threadlocal.get()================>执行线程 - 0
threadlocal.get()================>执行线程 - 3
threadlocal.get()================>执行线程 - 4
threadlocal.get()================>执行线程 - 5
threadlocal.get()================>执行线程 - 8
threadlocal.get()================>执行线程 - 7
threadlocal.get()================>执行线程 - 2
threadlocal.get()================>执行线程 - 6
process finished with exit code 0
threadlocal虽然叫线程局部变量,但是实际上它并不存放任何的信息,可以这样理解:它是线程(thread)操作threadlocalmap中存放的变量的桥梁。它主要提供了初始化、set()、get()、remove()几个方法。这样说可能有点抽象,下面画个图说明一下在线程中使用threadlocal实例的set()和get()方法的简单流程图。
假设我们有如下的代码,主线程的线程名字是main(也有可能不是main):
public class main {
private static final threadlocal local = new threadlocal<>();
public static void main(string[] args) throws exception{
local.set("doge");
system.out.println(local.get());
}
}
上面只描述了单线程的情况并且因为是主线程忽略了thread t = new thread()这一步,如果有多个线程会稍微复杂一些,但是原理是不变的,threadlocal实例总是通过thread.currentthread()获取到当前操作线程实例,然后去操作线程实例中的threadlocalmap类型的成员变量,因此它是一个桥梁,本身不具备存储功能
从thread源码入手:
public class thread implements runnable {
......
//与此线程有关的threadlocal值。该映射由threadlocal类维护。
threadlocal.threadlocalmap threadlocals = null;
//与此线程有关的inheritablethreadlocal值。该map由inheritablethreadlocal类维护
threadlocal.threadlocalmap inheritablethreadlocals = null;
......
}
从上面thread类源代码可以看出thread类中有一个threadlocals和一个inheritablethreadlocals 变量,它们都是threadlocalmap类型的变量,默认情况下这两个变量都是null,只有当前线程调用threadlocal类的iset或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是threadlocalmap类对应的get()、set()方法。
1.threadlocal的内部属性
threadlocalmap 的 key 是 threadlocal,但它不会传统的调用 threadlocal 的 hashcode 方法(继承自object 的 hashcode),而是调用 nexthashcode() ,具体运算如下:
public class threadlocal {
//获取下一个threadlocal实例的哈希魔数
private final int threadlocalhashcode = nexthashcode();
//原子计数器,主要到它被定义为静态
private static atomicinteger nexthashcode = new atomicinteger();
//哈希魔数(增长数),也是带符号的32位整型值黄金分割值的取正
private static final int hash_increment = 0x61c88647;
//生成下一个哈希魔数
private static int nexthashcode() {
return nexthashcode.getandadd(hash_increment);
}
...
}
这里需要注意一点,threadlocalhashcode是一个final的属性,而原子计数器变量nexthashcode和生成下一个哈希魔数的方法nexthashcode()是静态变量和静态方法,静态变量只会初始化一次。换而言之,每新建一个threadlocal实例,它内部的threadlocalhashcode就会增加0x61c88647。举个例子:
//t1中的threadlocalhashcode变量为0x61c88647
threadlocal t1 = new threadlocal();
//t2中的threadlocalhashcode变量为0x61c88647 0x61c88647
threadlocal t2 = new threadlocal();
//t3中的threadlocalhashcode变量为0x61c88647 0x61c88647 0x61c88647
threadlocal t3 = new threadlocal();
threadlocalhashcode是下面的threadlocalmap结构中使用的哈希算法的核心变量,对于每个threadlocal实例,它的threadlocalhashcode是唯一的。
这里写个demo看一下基于魔数 1640531527 方式产生的hash分布多均匀:
public class threadlocaltest {
public static void main(string[] args) {
printallslot(8);
printallslot(16);
printallslot(32);
}
static void printallslot(int len) {
system.out.println("********** len = " len " ************");
for (int i = 1; i <= 64; i ) {
threadlocal t = new threadlocal<>();
int slot = getslot(t, len);
system.out.print(slot " ");
if (i % len == 0) {
system.out.println(); // 分组换行
}
}
}
/**
* 获取槽位
*
* @param t threadlocal
* @param len 模拟map的table的length
* @throws exception
*/
static int getslot(threadlocal t, int len) {
int hash = gethashcode(t);
return hash & (len - 1);
}
/**
* 反射获取 threadlocalhashcode 字段,因为其为private的
*/
static int gethashcode(threadlocal t) {
field field;
try {
field = t.getclass().getdeclaredfield("threadlocalhashcode");
field.setaccessible(true);
return (int) field.get(t);
} catch (exception e) {
e.printstacktrace();
}
return 0;
}
}
上述代码模拟了 threadlocal 做为 key 的hashcode产生,看看完美槽位分配:
********** len = 8 ************
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
2 1 0 7 6 5 4 3
********** len = 16 ************
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3
10 1 8 15 6 13 4 11 2 9 0 7 14 5 12 3
********** len = 32 ************
10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 7 14 21 28 3
10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 7 14 21 28 3
process finished with exit code 0
2. threadlocal 之 set() 方法
threadlocal中set()方法的源码如下:
protected t initialvalue() {
return null;
}
/**
* 将此线程局部变量的当前线程副本设置为指定值。大多数子类将不需要
* 重写此方法,而仅依靠{@link #initialvalue}
* 方法来设置线程局部变量的值。
*
* @param value 要存储在此线程的thread-local副本中的值
*/
public void set(t value) {
//设置值前总是获取当前线程实例
thread t = thread.currentthread();
//从当前线程实例中获取threadlocals属性
threadlocalmap map = getmap(t);
if (map != null)
//threadlocals属性不为null则覆盖key为当前的threadlocal实例,值为value
map.set(this, value);
else
//threadlocals属性为null,则创建threadlocalmap,第一个项的key为当前的threadlocal实例,值为value
createmap(t, value);
}
//这里看到获取threadlocalmap实例时候总是从线程实例的成员变量获取
threadlocalmap getmap(thread t) {
return t.threadlocals;
}
//创建threadlocalmap实例的时候,会把新实例赋值到线程实例的threadlocals成员
void createmap(thread t, t firstvalue) {
t.threadlocals = new threadlocalmap(this, firstvalue);
}
上面的过程源码很简单,设置值的时候总是先获取当前线程实例并且操作它的变量threadlocals。步骤是:
- 获取当前运行线程的实例。
- 通过线程实例获取线程实例成员threadlocals(threadlocalmap),如果为null,则创建一个新的threadlocalmap实例赋值到threadlocals。
- 通过threadlocals设置值value,如果原来的哈希槽已经存在值,则进行覆盖。
3.threadlocal 之 get() 方法
threadlocal中get()方法的源码如下:
/**
* 返回此线程局部变量的当前线程副本中的值。如果该变量没有当前线程的值,
* 则首先通过调用{@link #initialvalue}方法将其初始化为*返回的值。
*
* @return 当前线程局部变量中的值
*/
public t get() {
//获取当前线程的实例
thread t = thread.currentthread();
threadlocalmap map = getmap(t);
if (map != null) {
//根据当前的threadlocal实例获取threadlocalmap中的entry,使用的是threadlocalmap的getentry方法
threadlocalmap.entry e = map.getentry(this);
if (e != null) {
@suppresswarnings("unchecked")
t result = (t) e.value;
return result;
}
}
//线程实例中的threadlocals为null,则调用initialvalue方法,并且创建threadlocalmap赋值到threadlocals
return setinitialvalue();
}
private t setinitialvalue() {
// 调用initialvalue方法获取值
t value = initialvalue();
thread t = thread.currentthread();
threadlocalmap map = getmap(t);
// threadlocalmap如果未初始化则进行一次创建,已初始化则直接设置值
if (map != null)
map.set(this, value);
else
createmap(t, value);
return value;
}
protected t initialvalue() {
return null;
}
initialvalue()方法默认返回null,如果threadlocal实例没有使用过set()方法直接使用get()方法,那么threadlocalmap中的此threadlocal为key的项会把值设置为initialvalue()方法的返回值。如果想改变这个逻辑可以对initialvalue()方法进行覆盖。
4.treadlocal的remove方法
threadlocal中remove()方法的源码如下:
public void remove() {
//获取thread实例中的threadlocalmap
threadlocalmap m = getmap(thread.currentthread());
if (m != null)
//根据当前threadlocal作为key对threadlocalmap的元素进行移除
m.remove(this);
}
这里罗列了 threadlocal 的几个public方法,其实所有工作最终都落到了 threadlocalmap 的头上,threadlocal 仅仅是从当前线程取到 threadlocalmap 而已,具体执行,请看下面对 threadlocalmap 的分析。
5.内部类threadlocalmap的基本结构和源码分析
threadlocalmap 是threadlocal 内部的一个map实现,然而它并没有实现任何集合的接口规范,因为它仅供内部使用,数据结构采用 数组 开方地址法,entry 继承 weakreference,是基于 threadlocal 这种特殊场景实现的 map,它的实现方式很值得研究。
threadlocal内部类threadlocalmap使用了默认修饰符,也就是包(包私有)可访问的。threadlocalmap内部定义了一个静态类entry。我们重点看下threadlocalmap的源码,
5.1先看成员和结构部分
/**
* threadlocalmap是一个定制的散列映射,仅适用于维护线程本地变量。
* 它的所有方法都是定义在threadlocal类之内。
* 它是包私有的,所以在thread类中可以定义threadlocalmap作为变量。
* 为了处理非常大(指的是值)和长时间的用途,哈希表的key使用了弱引用(weakreferences)。
* 引用的队列(弱引用)不再被使用的时候,对应的过期的条目就能通过主动删除移出哈希表。
*/
static class threadlocalmap {
//注意这里的entry的key为weakreference>
static class entry extends weakreference> {
//这个是真正的存放的值
object value;
// entry的key就是threadlocal实例本身,value就是输入的值
entry(threadlocal k, object v) {
super(k);
value = v;
}
}
//初始化容量,必须是2的幂次方
private static final int initial_capacity = 16;
//哈希(entry)表,必须时扩容,长度必须为2的幂次方
private entry[] table;
//哈希表中元素(entry)的个数
private int size = 0;
//下一次需要扩容的阈值,默认值为0
private int threshold;
//设置下一次需要扩容的阈值,设置值为输入值len的三分之二
private void setthreshold(int len) {
threshold = len * 2 / 3;
}
// 以len为模增加i
private static int nextindex(int i, int len) {
return ((i 1 < len) ? i 1 : 0);
}
// 以len为模减少i
private static int previndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
}
- 这里注意到十分重要的一点:threadlocalmap$entry是weakreference(弱引用),并且键值key为threadlocal实例本身,这里使用了无限定的泛型通配符。
- threadlocalmap 的 key 是 threadlocal,但它不会传统的调用 threadlocal 的 hashcode 方法(继承自object 的 hashcode),而是调用 nexthashcode()
5.2接着看threadlocalmap的构造函数
// 构造threadlocal时候使用,对应threadlocal的实例方法void createmap(thread t, t firstvalue)
threadlocalmap(threadlocal firstkey, object firstvalue) {
// 哈希表默认容量为16
table = new entry[initial_capacity];
// 计算第一个元素的哈希码
int i = firstkey.threadlocalhashcode & (initial_capacity - 1);
table[i] = new entry(firstkey, firstvalue);
size = 1;
setthreshold(initial_capacity);
}
// 构造inheritablethreadlocal时候使用,基于父线程的threadlocalmap里面的内容进行
// 提取放入新的threadlocalmap的哈希表中
// 对应threadlocal的静态方法static threadlocalmap createinheritedmap(threadlocalmap parentmap)
private threadlocalmap(threadlocalmap parentmap) {
entry[] parenttable = parentmap.table;
int len = parenttable.length;
setthreshold(len);
table = new entry[len];
// 基于父threadlocalmap的哈希表进行拷贝
for (entry e : parenttable) {
if (e != null) {
@suppresswarnings("unchecked")
threadlocal