ThreadLocal
版本:JDK7
ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键,任意对象为值的 键值对 存储结构。这个结构被附带在线程上,也就是说一个线程可以根据其内部的 ThreadLocal 对象查询到绑定在这个线程上的一个值。
下面来介绍一下 ThreadLocal 中几个重要的方法,了解了这些方法基本就知道 ThreadLocal 是如何运作的了。
1.1 get()
get() 是用来获取保存在当前线程中的副本变量。
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 根据获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 查找以ThreadLocal对象为键值的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
// 找到的话,就返回value
return (T)e.value;
}
// 返回初始值
return setInitialValue();
}
get() 内部调用了 getMap(Thread)、ThreadLocalMap.getEntry(ThreadLocal) 和 setInitialValue() 这三个方法,下面来逐个看看其细节实现。
getMap(Thread)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
顺着 t.threadLocals 找,你会发现原来这个 threadLocals 就是 ThreadLocal.ThreadLocalMap 的一个对象。
ThreadLocal.ThreadLocalMap threadLocals = null;
继续看,这个 ThreadLocalMap 里面存着什么? 这就需要看看下面调用的方法 getEntry(ThreadLocal)。
getEntry(ThreadLocal)
private Entry getEntry(ThreadLocal key) {
// 计算key对应的数组下标
int i = key.threadLocalHashCode & (table.length - 1);
// 获取entry
Entry e = table[i];
if (e != null && e.get() == key)
// 如果存在entry并且entry里面的ThreadLocal对象就是当前这个ThreadLocal对象的时候,返回entry
return e;
else
return getEntryAfterMiss(key, i, e);
}
setInitialValue()
private T setInitialValue() {
// 初始化本地线程变量,默认的方法是返回null,
// 需要子类实现需要初始化的变量
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// ThreadLocalMap已经初始化,将初始值设置进去
map.set(this, value);
else
// 初始化ThreadLocalMap,再将前面初始化的值设置进去
createMap(t, value);
// 返回初始值
return value;
}
void createMap(Thread t, T firstValue) {
// 当前线程的 ThreadLocalMap 就是在这里初始化的
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap(Thread, T)
这个方法为当前线程初始化了一个 ThreadLocalMap
对象。
1.2 set()
看过了get()具体实现,再来看set(T)方法就会发现很简单。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
看到这里应该能够明白 ThreadLocal 是如何为每个线程创建变量副本的:
-
每个 Thread 实例中都有一个 ThreadLocalMap 内部变量 - threadLocals。ThreadLocal 内部维护这一组 Entry 数组,数组中的元素就是用来储存实际变量副本的,键值为当前 ThreadLocal 对象,值为实际变量副本。
-
线程初始化的时候 threadLocals 为 null。只有在线程内调用 ThreadLocal 的 set 或者 get 方法的时候,才会去初始化 threadLocals,即 ThreadLocalMap 变量。
-
如果要用本地副本变量,可以调用 ThreadLocal 的 get 方法即可。
-
至于 ThreadLocalMap 内部为什么要维护一组 Entry[] 数组?其实,这个跟其他线程没什么关系。只是为当前线程多提供了一些本地线程变量(多 new 几个 ThreadLocal 对象可以看看)。
实战
Porfiler 这个类用于记录每个线程执行的时间。
public class Profiler {
public static final int MAX_THREADS = 5;
public static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i< MAX_THREADS; i++) {
new Thread() {
@Override
public void run() {
try {
Profiler.begin();
System.out.println(Thread.currentThread().getName() + " start to process job ...");
TimeUnit.SECONDS.sleep(1);
System.out.println("Current Thread : " + Thread.currentThread().getName() + " Cost : " + Profiler.end() );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
2.1 使用场景
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}