ThreadLocal是JDK提供的一个工具类,其作用是在多线程共享资源的情况下,使每个线程持有一份该资源的副本,每个线程的副本都是独立互不影响的。线程操作各自的副本,这样就避免了资源竞争引发的线程安全问题。
ThreadLocal,线程本地变量,该变量为每个线程私有。ThreadLocal类有一个内部类,名为ThreadLocalMap,可以理解为一个简化版的HashMap,它的Key为ThreadLocal实例,Value为ThreadLocal对象所引用的值,源码如下:
static class ThreadLocalMap {
//该Map的Entry,Key为ThreadLocal实例,Value为ThreadLocal对象所引用的值。
//这里使用了弱引用,当Entry为null时可以尽快被GC
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//初始容量16
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
}
ThreadLocalMap内部保存了众多的ThreadLocal对象,那么既然说ThreadLocal是线程私有的,那么ThreadLocalMap是存放在哪里呢?
Thread类有一个成员变量——threadLocals,它就是保存了与当前Thread关联的一个ThreadLocalMap,源码如下:
//当前线程内部维护的ThreadLocalMap对象,用于保存所有ThreadLocal实例
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到,ThreadLocalMap对象保存在了Thread的内部,也即当前线程的私有内存中。
ThreadLocal的主要方法为get()、set()和initialValue()。首先看set():
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程关联的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//创建一个Entry,以当前ThreadLocal对象为Key,待存储对象为Value,保存在ThreadLocalMap中
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,set()的逻辑很简单,从当前线程中获取ThreadLocalMap,然后将该ThreadLocal的值保存在里面。
再看get()方法:
public T get() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程关联的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//从ThreadLocalMap获取以ThreadLocal为key的Entry的value
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果当前ThreadLocalMap不存在,则调用setInitialValue()方法,获取初始值
return setInitialValue();
}
ThreadLocal还有一个方法initialValue(),该方法提供给子类覆盖,以在创建ThreadLocal时指定初始值。
ThreadLocal最常见的使用场景为管理数据库连接Connection对象等。Spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。
由于ThreadLocal对象会在每个线程中创建一个副本,所以在多线程环境下,如果ThreadLocal所保存的对象过大,可能导致内存的过多占用,需要引起注意。