每一个 Thread 线程对象都会维护一个自己的 threadLocals 和从父线程继承(使用浅拷贝)来的 inheritableThreadLocals。这两个字段是无法访问的,且都是一个 ThreadLocal.ThreadLocalMap 类,ThreadLocal 为键,任意对象为值。每次 new ThreadLocal() 并调用 ThreadLocal.set(T) 时就会从当前线程取出 threadLocals (ThreadLocal.ThreadLocalMap 类) 并把 ThreadLocal 做为键,传入的泛型 T 作为值。
public class Thread implements Runnable {
........
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; //本线程的 ThreadLocalMap
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //继承(浅拷贝)父线程的 ThreadLocalMap
........
}
以下是 Thread 执行的初始化逻辑:
private void init(ThreadGroup g, Runnable target, String name,long stackSize,
AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
// 当前线程就是该线程的父线程
Thread parent = currentThread();
this.group = g;
// 将daemon、priority属性设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
this.target = target;
setPriority(priority);
// 将父线程的InheritableThreadLocal复制过来(浅拷贝)
// main线程的inheritableThreadLocals为null
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.
inheritableThreadLocals);
// 分配一个线程ID
tid = nextThreadID();
}
1.ThreadLocalMap的存储结构
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;
}
}
private Entry[] table;
}
ThreadLocal.ThreadLocalMap 主要维护了 Entry[] 这个数组。Entry 是一个由(ThreadLocal,value)组成的二元组。
需要注意的是 Entry 继承了 WeakReference<ThreadLocal<?>> 弱引用,而这个弱引用指向的正是 ThreadLocal,通过 Entry 的构造方法可以看出:
Entry(ThreadLocal<?> k, Object v) {
super(k); // 调用 WeakReference 的构造,使弱引用指向 ThreadLocal
value = v;
}
因此,若没有其他引用指向这个 ThreadLocal,那么将在下一次 GC 时被回收,但是这个 ThreadLocal 对应的 value 却仍然在堆内存中,此时这个 Entry 为(null,vlaue),这就导致了 value 内存泄漏。
2.ThreadLocal的基本操作
由于 Thread 内部的 threadLocals(ThreadLocal.ThreadLocalMap 类)是私有的,并且没有提供任何操作它的方法,所以 ThreadLocal 就是操作 threadLocals 的媒介。本线程内调用的方法都可以通过 ThreadLocal 获取值。
通过 ThreadLocal 的 get 和 set 方法会从 threadLocals 里获取或设置其对应的 value:
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //操作当前线程的 threadLocals
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //操作当前线程的 threadLocals
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //会调用initialValue(),这是一个空方法,子类可以重写,可以更方便的设置value
}
通过匿名内部类重写 setInitialValue() 可以更方便地设置 value:
public ThreadLocal<Long> threadId = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return 1L;
}
};
以上代码相当于:
ThreadLocal<Long> threadId = new ThreadLocal<Long>();
threadId.set(1L);
3.ThreadLocal是线程安全的
通过以上 1、2 点的描述可以知道,ThreadLocal 的 get 和 set 方法,都是通过操作当前线程中的 threadLocals 完成数据读取和设置的,而 Thread 类中 threadLocals 是私有的,并且没有提供任何操作的方法,因此对于 threadLocals 的任何操作,都是在本线程内完成,没有其他线程竞争资源。
4.正确使用ThreadLocal
由于 ThreadLocal.ThreadLocalMap 使用弱引用保存 ThreadLocal 对象,所以不恰当的使用 ThreadLocal 会造成内存溢出(上面第 1 点有说明)。使用 ThreadLocal 可以参考以下两个原则(选择其一即可):
- ThreadLocal 申明为 private static final。
- static 说明是类变量,只有程序结束才会回收,也就是说始终有一个引用链到达这个 ThreadLocal 。
- final 说明这个引用不能被改变,如果改变就会导致这个 ThreadLocal 在内存中没有引用链到达。
- ThreadLocal 由一个弱引用指向,只要 ThreadLocal 没有引用链到达,就会在下一次 GC 被回收,导致其 value 泄漏在内存中。
- ThreadLocal使用后务必调用 remove方法。
- remove 后 ThreadLocal 的 value 也可以被回收了,就不会泄漏了。
示例:
public class ThreadLocalMemory {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return 100L;
}
};
public Long get() {
return threadLocal.get();
}
public void remove() {
threadLocal.remove();
}
}