持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
get方法
ThreadLocal中的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();
}
Thread t = Thread.currentThread(); get获取之前,从获取当前的线程对象,因为ThreadLocal保存的数据也都是属于当前线程的
getMap(t); 从当前线程中获取ThreadLocalMap对象
ThreadLocalMap.Entry e = map.getEntry(this); 从ThreadLocalMap中获取entry,entry中保存的就是具体的值
在ThreadLocalMap的getEntry方法中,方法内部首先是将参数的hash值与数组大小取模并减一,作为索引位置查询数据,如果找不到对应的数据(数据为空)则自选将索引+1继续寻找,直到找到为止。因为这种不断的自旋找法,如果使用了大量的key的话,数据获取的效率就会很低。
扩容方法
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
Entry[] oldTab = table; 先拿出旧的数组
int newLen = oldLen * 2; 扩容的新数组为旧数组大小的2倍
for (int j = 0; j < oldLen; ++j) { ... } 将老数组的值拷贝到新数组中
setThreshold(newLen); 设置数组新的扩容阈值,阈值为数组长度的三分之二
这里的扩容不像ArrayList或者其他线程安全的集合扩容方法,这里没有考虑modCount,也就是没有考虑到线程不安全的情况,这是因为本来这就是线程的独属的方法,只有一个线程,也就是线程本身才能操作这部分的数据,所以不会存在线程不安全的情况。
总结
ThreadLocals.ThreadLocalMap 和 InheritableThreadLocals.ThreadLocalMap 是线程独有的属性,每个线程的ThreadLocal都是隔离的,所以是线程安全的。
当在一个线程中创建另一个线程时,也就存在父子线程的关系,但是子线程的ThreadLocal不会和父线程的ThreadLocal共享。