ThreadLocal类型的变量为线程本地变量,大概的原理就是建立了一个Map,然后key是线程,value是指,通过get方法就能取到每个线程自己的变量副本
1. ThreadLocal使用
2. ThreadLocal源码分析
public class ThreadLocal<T> {
// 设置属性
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 找到当前线程里的map
if (map != null)
map.set(this, value); // 将 键值对 threadLocal的this 和 value 放入到当前线程的map中
else
createMap(t, value);
}
// 获取属性
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 通过 threadLocal的this 获取到value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// 得到map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//将数据从map中移除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); // 通过 threadLocal的this 将键值对移除
if (m != null) {
m.remove(this);
}
}
}
通过源码分析,真正存储数据的地方是Thread的threadLocals 类型为ThreadLocalMap
- key: threadLocald对象
- value: 要存储的值
本来我以为ThreadLocal是一个map,里面存储了Thread和value的键值对,如下表
ThreadLocal
key | value |
---|---|
线程1 | 值 |
线程2 | 值 |
线程3 | 值 |
但是真实的存是如下表
Thread
key | value |
---|---|
threadLocal1 | 值 |
threadLocal2 | 值 |
threadLocal3 | 值 |
3. ThreadLocal问题
3.1. ThreadLoca置空无法回收的内存泄漏问题
使用ThreadLocal
变量时,在使用完毕之后一定要调用remove
方法将变量移除,不然会出现线程安全的问题。
就算移除了,然后把ThreadLocal local = null; 置空,但是还有Thread里的map持有local的引用,所以local指向的堆已经对我们不可用了,就产生了内存泄漏
记住是内存泄漏,不是内存溢出 而且泄露的是local对象本身。
但是这个问题已经使用弱引用解决了
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
虽然是Entry继承自WeakReference,但是只有key是若引用类型,这点一定要理解
当用户把local置空,引用断开,在回收的时候Entry里的引用也会断开,至此,local对应的堆已经没有引用指向了,所以会被gc回收掉
3.2. Value无法回收的内存泄漏问题
在上面我们解决了key内存泄漏的问题,但是同样的,就算key被置空,键值对变成了 null=value ,value还是不会被回收
而且value也没有使用弱引用,而是强引用,所以需要用户手动调用remove来移除value