- 包路径:package java.lang; 并不在java.util里
- ThreadLocal实例以及数据的存放位置:ThreadLocal实例被创建类持有,包含ThreadLocal实例以及对应的变量副本的ThreadLocalMap变量归属当前线程实例,所以ThreadLocal实例以及数据都存放在堆上。
- 使用ThreadLocal的作用:为每个线程提供一个线程私有的变量副本,解决了变量并发访问的冲突问题
- Thread内部有ThreadLocalMap类型的变量threadLocals
- ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了数组下标,通过线性探测依据下标确定数据的实际存放位置
图片来自 https://www.zhihu.com/question/341005993
Thread 类里有一个名为threadLocals的ThreadLocalMap类型的属性
ThreadLocal本身不存储数据,存储数据的是ThreadLocal类里的ThreadLocalMap,ThreadLocal的作用是作为ThreadLocalMap的key值。
2.2.1 get方法 public T get() {
// 获取当前线程实例
Thread t = Thread.currentThread();
// 获取当前线程实例中的 名为threadLoals 的 map
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以当前 ThreadLocal实例为 key 获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// Entry 不为空 返回 value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
getMap
// 上面get方法里的 getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getEntry
private Entry getEntry(ThreadLocal> key) {
// 先计算找到下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果对应位置有值,并且key == k,返回这个entry
if (e != null && e.get() == key)
return e;
// 否则要进行遍历
else
return getEntryAfterMiss(key, i, e);
}
// 上一个方法没找到
private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 向后遍历table数组 直到找到 k == key ,也就是存放当前ThreadLocal实例以及对应数据的entry
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
// 这个东西 清除过期的entry(key == null,但是 value != null)
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
2.2.2 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);
}
2.2.3 remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
2.3 ThreadLocalMap
ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal用来确定数组下标(下标一样的话),依据这个下标获取value存储的对应位置
2.3.1 底层存储数据的数据结构 Entry[ ] 这是一个数组- 根据 ThreadLocal 确定数组下标
static class Entry extends WeakReference内存泄漏问题> { Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } } // map数组 private Entry[] table;
- WeakReference是弱引用,不论内存空间是否足够,只要JVM进行GC弱引用对象就会被回收,会出现Map里的Key被回收但是Value依然存在的情况,如果这个线程一直存活,value一直得不到回收,就发生内存泄漏。
- 使用完调用remove方法来解决这种情况
- 以ThreadLocal实例为key,要操作的数据对象为value
- 解决哈希冲突的方法:开放定址法、链地址法,此处使用开放定址法的线性探测
//
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
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;
// 获取哈希值 根据唯一哈希值 获取Entry的位置 i
int i = key.threadLocalHashCode & (len-1);
// 从Entry数组table的位置i开始向后遍历
// 如果 e!=null 进入循环 e
// 如果 e==null 直接跳过循环
for (Entry e = tab[i] ; e != null ; e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
// 进行此判断的原因是 哈希值相同 两个对象不一定相同
if (k == key) {
// 如果key相同,用新值覆盖旧值,然后返回
e.value = value;
return;
}
// entry[i]不为null 但是key是null,GC过后key被回收了 但是value依然存在
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
// 这里的说明在 https://www.zhihu.com/question/412041096
// 如果 e!=null && e.get!=null && e.get != key 说明哈希冲突了
// i = nextIndex(i,len)
// 然后再判断 table[i] ?= null
// 也就是说向后寻找空位置,或者k 和 key相同的位置,放入新值
}
// table[i] == null
// 直接新建一个Entry 放到table[i]
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}



