常用方法
(1).set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法的代码非常短,首先我们是获取的是当前的线程,然后调用getMap方法来获得TreadLocalMap对象,再来看看getMap方法获取的是什么:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap很简单,就是返回线程中ThreadLocalMap,跳到Thread源码里看,ThreadLocalMap是这么定义的:
ThreadLocal.ThreadLocalMap threadLocals = null;
后续的get方法也是会用到ThreadLocalMap,后续一起研究。
(2).get方法:
在使用ThreadLocal类时,get方法也是不可避免的,通常我们调用get方法来获取在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();
}
get方法也是非常的简单,最终还是到了ThreadLocalMap里面去了。
ThreadLocalMap
可以参考上节内容:源码分析–ThreadLocal(一)
现在主要看方法:
ThreadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) {
//首先获取当前保存的数据table数组
Entry[] tab = table;
//获取数组的长度
int len = tab.length;
//将需要保存的ThreadLocal进行哈希计算出索引
int i = key.threadLocalHashCode & (len-1);
//先看看这个for循环进入和跳出的条件是什么,进入for循环的条件是获取的e不为空,不为空后进入for循环判断取出的key
//是否和当前的ThreadLocal是否相同如果相同,那么就重新赋值并返回。如果不相同判断下k值是否存在,如果不存在但是table[i]还有值
//只有一种情况那就是这个table[i]保存的ThreadLocal被垃圾回收掉了,Entry是弱引用,当不存在使用时会被回收掉,那么就进行替换并赋值
//跳出for循环的条件是获取的tab[i]为空值,如果为空那么直接就赋值就可以了,这个条件很重要,后面的操作都是因为这个条件才进行的
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//取出的Entry有值,判断保存的ThreadLocal和将要保存的ThreadLocal是否相同
//如果相同直接赋值返回
if (k == key) {
e.value = value;
return;
}
//如果取出的Entry为空值进行赋值并返回
//这个时候说明改table[i]可以重新使用,用新的key-value将其替换,并删除其他无效的entry
if (k == null) {
//key值替换成新的ThreadLocal值,下面会进行讲解
replaceStaleEntry(key, value, i);
return;
}
}
//跳出for循环,说明tab[i]为空值,赋值就可以了
tab[i] = new Entry(key, value);
//当前的table大小加1
int sz = ++size;
//cleanSomeSlots用于清除那些e.get()==null,也就是table[index] != null && table[index].get()==null
//之前提到过,这种数据key关联的对象已经被回收,所以这个Entry(table[index])可以被置null。
//如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行rehash()
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这块理解起来较为复杂,可以参考https://zhuanlan.zhihu.com/p/266525527
set方法的目的,我们都知道,就是将一个键值对成功保存在Entry数组里面,而保存的index不是指定,而是通过key的hashCode来计算的。将一个key的hashCode通过一个hash函数得到一个index值,而这个值就是这个键值对在数组里面的位置。
大致总结一下:
1.在set方法里面,将key的hashCode对len进行取模运算来获取index。这里需要注意的是len必须是2的n次方。
2.如果发生了哈希碰撞了,set方法采用的是开发地址方法来解决的。
3.在进行开放地址方法时,有可能会出现key被回收的情况,这里会可能会导致内存泄露的问题。官方的手段是,对key为null的Entry进行清理。
其他方法后续学习补充~