在多线程情况下,在线程类中使用ThreadLocal可以为每个线程配置私有的对象
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
比如可以这样使用,用 ThreadLocal 保存用户信息,里面的值可以存放用户信息,不是公用属性不会存在多线程并发问题:
private static ThreadLocal<LoginUserInfo> USER_INFO_CACHE = new ThreadLocal();
他们使用 get()和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题
这个类中只有一个ThreadLocal对象,但是每一个线程中都有不同的ThreadLocalMap
最终的变量是放在了当前线程的ThreadLocalMap中,并将ThreadLocal这个对象的弱引用作为键
//ThreadLocal的set方法可以说明一切
public void set(T value) {
//这个t代表当前线程
Thread t = Thread.currentThread();
//ThreadLocalMap是ThreadLocal的内部类,这让每个线程都有一个map
ThreadLocalMap map = getMap(t);
//map没有得到,创建一个当前线程的map
//有map则向map中存值
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocalMap类似hashmap,但是所使用的hash函数、碰撞处理等方法大不相同
碰撞处理使用动态寻址法
ThreadLocalMap中存储的是entry,这个是ThreadLocalMap的内部类,它是一个ThreadLocal类型的弱引用,在map中充当键的位置
在ThreadLocalMap.set()方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中Entry的数量已经达到了列表的扩容阈值(len*2/3),就开始执行rehash()逻辑,也就是扩容处理
让一个虚引用当值主要是为了防止内存泄漏,当ThreadLocal需要被回收的时候,如果在map中的键是强引用,那么这个对象是无法被回收掉的
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal需要我们解决的的内存泄漏问题是下面这种
因为ThreadLocalMap的键为弱引用,值为强引用,每次GC后,键的指向都为null(因为ThreadLoacl被回收掉了,但是这个弱引用指针并没有被回收),值的指向对象依然没有被回收,产生了OOM问题
解决办法是在使用完毕后调用remove方法手动清除
当Map中的键指向null的时候代表这个数据过期了,需要被回收掉,ThreadLocal提供了俩种过期删除策略
1,探测式清理操作
2,启发式清理