之前在看源码的时候,忽略了这个知识点,但是在后面看到框架底层源码的时候,看到spring的事务有用到这个点,所以就再回头学习下threadLocal的原理
首先threadLocal是线程本地变量的意思,主要用来做线程数据隔离的操作,在使用的过程中,我们主需要关注threadLocal就可以,但是在底层源码层面,需要设计到几个相关的知识:
thread、threadLocal、threadLocalMap三者之间的关系
1、thread对象持有了一个threadLocalMap,但是对threadLocalMap的set、get、remove都是通过threadLocal来进行操作的
2、threadLocalMap的key是threadLocal对象的指针,value是我们程序员要设置的值
弱引用
我们通常说threadLocalMap是弱引用,如何体现?
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这是threadLocalMap的entry对象,可以看到,是继承了weakReference,是弱引用
set
在要做线程数据隔离的时候,通常我们调用的是threadLocal.set()方法,来看下源码
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* 这是设置当前线程绑定的局部变量
* 1.获取到当前线程
* 2.根据线程,获取到thread中所维护的threadLocalMap
* 3.如果map不为null,就将当前threadLocal作为key,设置到线程维护的threadLocalMap中
* 4.如果map为null,就创建一个新的threadLocalMap,并将key和value赋值进去
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这里的getMap(t),就是调用thread中的threadLocalMap
set(this,value):调用的是threadLocalMap的set方法,关于map的set方法后面单独介绍
get
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
* 这里应该是根据当前线程从map中获取到当前线程对应的value
*/
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();
}
这里可以看到,在调用get()方法的时候,会根据当前线程从thread中获取到对应的threadLocalMap,我觉得这里就是为什么可以隔离的原因,不同的thread,获取到的是不同的threadLocalMap,如果当前线程对应的entry不为null,就返回对应的value
threadLocalMap.set()
接着来看下threadLocalMap的set方法是如何存入一个元素的
/**
* Set the value associated with key.
* 这里是向集合中插入元素的源码,这里和hashmap是不同的处理思想
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
/**
* 1.这里的threadLocalHashCode是根据atomicInteger来获取值的
* 每调用一次set方法,atomicInteger ++;
*/
int i = key.threadLocalHashCode & (len-1);
/**
* 2.判断i位置是否已经存在元素
* 如果i != null,就通过nextIndex获取到i+1位置的元素,需要注意的是:如果 i+1 > len,就会从0开始
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
/**
* 2.1 如果key相同,直接覆盖
*/
if (k == key) {
e.value = value;
return;
}
/**
* 2.2 如果i位置的key为null,就直接用当前key和value,写入到i位置
* 这里就和弱引用有点关系了;
* 如果key是弱引用,在使用完之后,如果我们没有调用threadLocal.remove()方法,key会被回收,因为key是弱引用
* 但是value不会被回收,此时就出现了key为null,但是value依旧有值,如果是这种情况,在源码中就可以直接进行覆盖
*
* 但是反之,如果我们key使用的是强引用,即使使用完了,key也不会被回收,依旧不是null,在不是null的情况下,这里如何判断key是已经使用完了?还是没有使用完?
* 所以:对于使用弱引用来说,即使我们没有调用remove方法,在一定程度上,可以通过key为null,value不为null这个场景来减少内存溢出的场景
*/
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
/**
* 3.cleanSomeSlots是清除那些e.get() == null的元素
* 如果清除失败,且数组长度超过阈值,就扩容
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这里threadLocalMap在处理冲突的时候,采用的是开放寻址法,如果i位置冲突,就去i+1位置判断,是否可以放入到,如果i+1 大于数组长度,那就从0位置继续开始
threadLocalMap和hashmap
其实整个threadLocalMap的源码看下来,和hashmap是有很大的区别的
1.threadLocalMap的key是threadLocal对象
2.threadLocalMap的hash值的计算,是根据atomicInteger来计算的
3.在处理冲突的时候,threadLocalMap采用开放寻址法,hashmap采用链地址法
内存溢出问题
threadLocal如果使用不当会有内存溢出问题,既然threadLocal的key采用的是弱引用,为什么还会有内存溢出?因为key虽然是弱引用,但是value是强引用,在使用完了之后,key(threadLocal)会被回收,但是value依旧存在,所以会存在无法回收的场景
那如果map的key采用强引用,也会导致无法回收,但是如果使用弱引用,会有一个好处:
在往map中插入元素的时候,如果i位置的key不存在,但是value存在,那此时就会认为该位置可以进行覆盖,因为key为null,value不为null,就是被回收的场景,只是value是强引用,无法被回收
但是如果key和value都是强引用的话,那在set的时候,就无法区分key是使用完了,还是未使用完,所以:key使用弱引用,可以在一定程度避免内存溢出问题
所以,最好是,在使用完,就调用threadLocal的remove方法,将key和value从map中移除
总结
所以,总结而言:
1、对于threadLocal的使用,最好是在使用完了之后,就手动的remove
如果不remove,那由于threadLocalMap的特性,在使用完之后,会对key进行释放,在下次进行插入的时候,会对该位置进行覆盖,只是在一定程度上,可以避免内存溢出
2、threadLocalMap在冲突的时候,采用的是开放寻址法,key的hash值,是通过一个atomicInteger变量来计算的