Java ThreadLocal
jdk1.2中引入了threadLocal,作为线程私有变量,每个线程持有不同的ThreadLocal的静态内部类ThreadLocalMap的实例引用。
ThreadLocalMap,ThreadLocal的一个静态内部类,用ThreadLocal作为key值,默认容量为16,超过2/3阈值时则扩容为原来的两倍。
ThreadLocal 常用方法
- 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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
- 获取当前线程的threadLocalMap,不为空则直接设值,为空则初始化一个ThreadLocalMap。
- get方法
public T get() {
Thread t = Thread.currentThread();
//1.获取Thread的ThreadLocalMap
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;
}
}
//3.如果线程的threadLocalMap为空,则设置初始化的值,最终会调用initialValue方法(该方法可以在创建ThreadLocal时重写)
return setInitialValue();
}
- map.set和map.getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//循环查找数组中的entry
return getEntryAfterMiss(key, i, e);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//1.计算key所在的位置
int i = key.threadLocalHashCode & (len-1);
//2.查找key值所在的位置的数据
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//3.如果key已经存在,则替换相应的value
if (k == key) {
e.value = value;
return;
}
//4.如果key值为空,ThreadLocal的引用已经不存在,则这个过期的entry值替换成对应的key、value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//5.清除过期的entry后如果map的容量大于扩容阈值的话,则进行扩容为原来的两倍
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
HASH_INCREMENT = 0x61c88647
-
hash冲突问题
- 当同一个Thread中的新建多个ThreadLocal的实例时。
- 同一个ThreadLocal设置多次值时。
后者发生时直接替换掉原来的值,前者发生时则是通过threadLocalHashCode增长指定的值(HASH_INCREMENT)来探测下一个位置是否可用,即线性探测。
-
为什么是 0x61c88647?
每创建一个ThreadLocal时threadLocalHashCode都会增长0x61c88647,而0x61c88647的步长能让hash码均匀的分布在2的N次方的数组中。
0x61c88647是2^31的黄金比例值。
public static void main(String[] args) {
long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));
System.out.println("as 32 bit unsigned: " + l1);
int i1 = (int) l1;
System.out.println("as 32 bit signed: " + i1);
System.out.println("MAGIC = " + 0x61c88647);
}
Why 0x61c88647? https://www.javaspecialists.eu/archive/Issue164.html
ThreadLocal内存泄漏问题
尽管ThreadLocalMap中的Entry是弱引用类型,Thread.exit()时会将线程持有的ThreadLocalMap变量置为null,但还是会存在内存泄漏问题。
当线程长时间存活的场景下(例如使用线程池),所以建议是用ThreadLocal的remove方法,在业务逻辑处理完成时手动清除当前线程的ThreadLocal。