ThreadLocal

ThreadLocal

每个线程单独存储自己的数据,具有线程隔离的效果

原理:

每一个Thread维护一个ThreadLocalMap,通过ThreadLocal这个载体,在使用时变量为空则先创建ThreadLocalMap并赋值给线程再使用;ThreadLocalMap内部维护table数组,所有的数据都存在这里。

1
2
3
4
5
6
7
8
9
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {// 这里有内存泄漏的风险
super(k);
value = v;
}
private Entry[] table;
}

所以对应的关系如下:

1
2
线程与ThreadLocal:一对多
线程与ThreadLocalMap:一对一

值得注意的是,每次new ThreadLocal时,都会对散列code自增一个固定值,来使算出的结果更为分布

1
2
3
4
5
6
7
8
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();// 每new一个都会自增一个固定值
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
解决哈希冲突的方式

使用开放地址法解决哈希冲突

哈希冲突解决方式:

  1. 开放地址法:即假设索引15已经存在值且key不同,则会查看索引16为空则设置进去,否则继续下一个索引
  2. 链表法:在冲突的地方以链表的形式存储:HashMap
  3. 再哈希法:使用不同的哈希函数再次哈希直到不再冲突为止,缺点是增加了计算哈希的时间
  4. 建立公共溢出区:当发生冲突时,将冲突的数据统一放到溢出区
内存泄漏的原因

如果一个ThreadLocal不存在外部强引用,则key会被GC回收,这样会导致ThreadLocalMap中的tables数组里key为null,而value为强引用;除非线程退出或者调用get()/set()/remove()等方法触发清除,才会断开对value的强引用。

总结就是因为ThreadLocalMap的生命周期跟Thread一样长,没有手动删除数据造成的内存泄漏

解决方式:每次用完ThreadLocal时都调用remove()方法清除数据集