1、是什么?
线程类中声明了一个ThreadLocal.ThreadLocalMap类型的成员变量,是为了维护线程本地变量而设计的。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
ThreadLocal,即线程局部变量,提供了线程本地实例。同一个ThreadLocal 所包含的对象,在不同的Thread中有不同的副本,即不同线程之间是隔离的,不存在多线程间的共享问题。
ThreadLocal是一个泛型类,保证可以接受任何类型的对象。ThreadLocalMap是对应的一个静态内部类,以ThreadLocal对象为键、任意对象为值的存储结构。
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
....
}
}
使用set()、get()方法可以操作对应当前线程ThreadLocal对应的Value
public T get() {
Thread t = Thread.currentThread();
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;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 将当前ThreadLocal作为key
map.set(this, value);
} else {
createMap(t, value);
}
}
// 获取当前线程的threadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
2、内存泄漏问题
ThreadLocalMap的Entry类中key为ThreadLocal的弱引用,特点就是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
java对象的引用包括 : 强引用,软引用,弱引用,虚引用 。
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它,当内存空间不足时,
Java
虚拟机宁愿抛出OutOfMemoryError
错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
线程扫描内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样 ThreadLocalMap中使用这个 ThreadLocal 的 key也会被清理掉。但是value 是强引用,不会被清理,就会出现 key 为 null 的 value,当value是很大的对象就会存在内存泄漏风险。
ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。
那为什么还要设计成弱引用?