一、背景
最近有人问我ThreadLocal
是如何做到在每个线程中的值都是隔离的,此处写篇文章来简略记录下。
二、ThreadLocal解决的问题
- 该数据属于该线程
Thread
本身,别的线程无奈对其影响。(须要留神:须要调用ThreadLocal的remove办法) - 不存在线程平安问题。(因为
ThreadLocal
类型的变量只有本身的线程能够拜访,所以这点是成立的。)
比方:
用户登录胜利后,须要将登录用户信息
保存起来,以不便在零碎中的任何中央都能够应用到,那么此时就能够应用ThreadLocal
来实现。例如:Spring Security
中的ThreadLocalSecurityContextHolderStrategy
类。
三、如何创立一个ThreadLocal实例
private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();
ThreadLocal的实例举荐应用private static final
来润饰。
四、ThreadLocal如何做到线程变量隔离
1、了解3个类
ThreadLocal
: 此类提供了一个简略的set
,get
,remove
办法,用于设置,获取或移除 绑定到线程本地变量中的值。-
ThreadLocalMap
: 这是在ThreadLocal中定义的一个类,能够简略的将它了解成一个Map,不过它的key是WeakReference弱援用类型,这样当这个值没有在别的中央援用时,在产生垃圾回收时,这个map的key
会被主动回收,不过它的值不会被主动回收。static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { // key 弱援用 super(k); // 值强援用 value = v; } }
Thread
:这个是线程类,在这个类中存在一个threadLocals
变量,具体的类型是ThreadLocal.ThreadLocalMap
。
2、看下set办法是如何实现的
public void set(T value) {
// 获取以后线程
Thread t = Thread.currentThread();
// 获取绑定到这个线程本身的 ThreadLocalMap,这个ThreadLocalMap是从Thread类的`threadLocals`变量中获取的
ThreadLocalMap map = getMap(t);
if (map != null) {
// 向map中设置值,key为 ThreadLocal 对象的实例。
map.set(this, value);
} else {
// 如果map不存在,则创立进去。
createMap(t, value);
}
}
通过上方的代码,咱们可知: 当咱们向ThreadLocal中设置一个值,会通过如下几个步骤:
- 获取以后线程
Thread
- 获取以后线程的
ThreadLocalMap
对象。 - 向
ThreadLocalMap
中设置值,key为ThreadLocal
对象,值为具体的值。
3、看看 get 办法如何实现
public T get() {
// 获取以后线程
Thread t = Thread.currentThread();
// 获取这个线程本身绑定的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// this是ThreadLocal对象,获取Map中的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取具体的值
T result = (T)e.value;
return result;
}
}
// 设置初始值
return setInitialValue();
}
从上方的get 和 set 办法能够得悉,通过往ThreadLocal对象中设置值或获取值,其实是最终操作到Thread对象中的threadLocals字段中,而这个字段是Thread本身的,因而做到了隔离。
五、ThreadLocalMap中的hash抵触是如何解决的
1、ThreadLocal对象的hash值是怎么的
private final int threadLocalHashCode = nextHashCode();
// 该 ThreadLocal 对象本身的hash code值
private final int threadLocalHashCode = nextHashCode();
// 从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每次递增固定的值
private static final int HASH_INCREMENT = 0x61c88647;
// hash code 值计算
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
从上方的代码中能够,ThreadLocal
类在实例化进去之后,它的hash code值(threadLocalHashCode)
就是固定的,即便ThreadLocal
调用了set
办法,设置了别的值,它的hash code值
也不会发生变化。
此字段threadLocalHashCode
为ThreadLocal
对象的hash值,在ThreadLocalMap
中须要用到这个hash值。
2、解决hash抵触
ThreadLocalMap
解决hash抵触的方法很简略。就是通过线性探测法。如果产生了抵触,就去找数组前面的可用地位。具体看上图。演示的是A和B 2个ThreadLocal对象,而后产生了抵触,A和B存在的地位在那个中央。
六、ThreadLocal内存透露
ThreadLocal为什么会存在内存透露呢?
这是因为ThreadLocalMap
中的key
是WeakReference
类型,也就是弱援用类型,而弱援用类型的数据在没有内部强援用类型的话,在产生gc
的时候,会主动被回收掉。留神:
此时是key
被回收了,然而value
是没有回收的。因而在ThreadLocalMap
中的Entry[]
中可能存在key
是null
,然而value
是具体的值的对象,因而就产生了内存透露。
解决内存透露:
当咱们应用完ThreadLocal
对象后,须要在适当的机会调用ThreadLocal#remove()
办法。 否则就只有等Thread
主动退出能力革除,如果是应用了线程池,Thread
会重用,革除的机会就更难。
发表回复