前言
上一篇文章介绍了ThreadLocal的相关类结构和基本使用案例,这篇文章以这个案例为基础介绍下常用的相关方法。
ThreadLocal构造方法set方法get方法remove()方法
ThreadLocal构造方法
public ThreadLocal() {
}
ThreadLocal只有一个无参构造方法,只是创建了对象没有做其他操作。
set方法
// java.lant.ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// java.lant.ThreadLocal
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 获取Thread类中的ThreadLocalMap类型属性
}
// java.lant.Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
首先获取当前线程,然后根据当前线程去获取线程中存储的ThreadLocalMap,而Thread类中threadLocals默认是null,所以上面的if条件判断会有以下两种情况。
首次调用
首次调用set方法时threadLocals是null,所以执行createMap方法进行初始化。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // 初始化table
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // hash值计算索引
table[i] = new Entry(firstKey, firstValue); // 存储数据
size = 1;
setThreshold(INITIAL_CAPACITY); // 设置阈值
}
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
createMap方法只做了一件事,就是创建一个ThreadLocalMap然后赋值给当前线程的threadLocals属性。
继续向下看ThreadLocalMap的构造方法,首先创建了一个Entry数组对table属性进行初始化,然后根据ThreadLocal对象的hash值来计算在数组中的索引,创建一个Entry对象来存储数据放入该索引位置。
size = 1设置的是当前table中存储的数据数量。
setThreshold方法设置table的阈值为数组长度的2/3,当达到该阈值时会触发扩容。
非首次调用
非首次调用时获取的ThreadLocalMap对象不为null,所以直接尝试存储数据。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; // 获取数组
int len = tab.length; // 数组长度
int i = key.threadLocalHashCode & (len-1); // 计算索引位置
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 找到了之前存的
e.value = value;
return;
}
if (k == null) { // 过时的Entry,已经被移除
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这个方法整体上分为两个步骤:for循环部分和for循环之后的处理。
第一步:根据key的hash值计算出索引位置,获取该位置的Entry,并取出存储的key
- 如果刚好是要存储的
key,那就直接用新的value覆盖掉旧数据; - 如果
key是null,表明该Entry已经过期,就清除掉过期的Entry并替换为新的Entry(replaceStaleEntry方法后面会有文章单独介绍)。 - 如果上面两个条件都不满足,就获取下一个索引位置的
Entry一直重试。
第二步:如果不满足for循环的条件,说明该索引位置是空的,所以就可以创建一个Entry直接放进去,数组中元素数量size加1。然后清除过期的Entry并判断数组中元素数量是否超过了阈值,如果超过了就进行扩容。
get方法
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对象,如果是null就调用setInitialValue方法进行初始化并把初始值返回。如果不为null就根据ThreadLocal对象去查找Entry,如果找到了就取出里面的值返回。
// 初始化
private T setInitialValue() {
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;
}
setInitialValue方法首先通过initialValue方法获取一个初始值,默认是null,然后获取当前线程的ThreadLocalMap对象,获取成功就把初始值保存进去,否则就创建一个新的ThreadLocalMap把初始值保存起来。这里的初始值可以通过子类重写的方式来返回想要的对象。
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); // 移除操作
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 计算hash
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) { // 找到了
e.clear(); // 清除key
expungeStaleEntry(i); // 清除陈旧的Entry
return;
}
}
}
// 祖父类Reference
public void clear() {
this.referent = null;
}
首先获取当前线程中存储的ThreadLocalMap变量,如果存在就尝试进行移除操作。
根据hash值找到当前key所在的索引位置,然后取出该位置的Entry,如果刚好就是我们要找的对象,就调用clear方法执行清除操作,然后清除掉陈旧的Entry。如果不是要找的对象,就获取下一个索引继续重试,直到找到该对象或者遍历完数组为止。
这里可以看到clear方法其实就是把referent(原来存的ThreadLocal对象,referent介绍)置为null,所以调用Entry的get()方法时就会出现null的情况。expungeStaleEntry方法后面会单独介绍。