ThreadLocal 顾名思义“线程本地变量”,对应到 Java 代码就是线程私有变量,可以把它理解为对于同一个变量,在不同的线程包含不同的副本,并且各个副本之间相互独立
下面我们通过一个示例简单认识 ThreadLocal:
public class ThreadLocalTest {
ThreadLocal<Long> lLocal = new ThreadLocal<>();
ThreadLocal<String> sLocal = new ThreadLocal<>();
public void set() {
lLocal.set(Thread.currentThread().getId());
sLocal.set(Thread.currentThread().getName());
}
public void get() {
System.out.println(lLocal.get());
System.out.println(sLocal.get());
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalTest test = new ThreadLocalTest();
test.set();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test.set();
test.get();
}
});
t1.start();
t1.join();
test.get();
}
}
上述代码包含两个线程,main 线程和 t1 线程,两个线程都调用了相同对象的 set()、get() 方法,这两个方法分别向线程本地变量中放入和取出数据,其中放入的是自身线程的 ID 和 线程名称,执行结果如下所示:
9
Thread-0
1
main
从结果可以看出:虽然两个线程调用同一个对象的同一个方法,但取到的结果因线程而异,后执行线程的 set() 操作并没有覆盖前执行线程已经放入的值,也就是说:值是线程私有的。
通过示例也可以看出,ThreadLocal 的使用主要基于 get()、set() 方法,下面我们直接看源码:
public void set(T value) {
// 获取调用 set() 方法的线程
Thread t = Thread.currentThread();
// 获取一个 map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这里首先获取当前线程对象,根据线程对象获取 ThreadLocalMap 对象,之后以 this 为 key,value 为值,存入 map。这里 this 指代当前 ThreadLocal 对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从这里也就可以看出,ThreadLocalMap 对象实际是 Thread 对象的属性变量,其中它的定义如下:
ThreadLocal.ThreadLocalMap threadLocals = null;
而 ThreadLocalMap 又是 ThreadLocal 类的内部类,也就是说,set 操作实际是基于 ThreadLocalMap 来实现的。如果 Map 还没有初始化,就调用 createMap() 方法,它的源码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
还是 ThreadLocalMap ,绕来绕去,最终都会调用到这个内部类,下面我们再看 get() 方法:
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();
}
从代码可以看出,这里也是基于 this 从 ThreadLocalMap 中获取数据,查不到调用 setInitialValue() 方法:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在 setInitialValue() 方法中,初始化 map,初始值默认为 initialValue() 方法:
protected T initialValue() {
return null;
}
默认值是 null,一般重写该方法手动初始化默认值。到这里就可以看出 ThreadLoacl get()、set() 都基于 ThreadLocalMap 实现。我们简单总结一下:
- ThreadLocal 基于 ThreadLocalMap 实现,ThreadLocalMap 对象是线程类属性变量,实现在 ThreadLocal 中
- ThreadLocalMap 中 key 是 ThreadLocal 对象,value 才是具体副本值
也就是说,所有的线程都包含 ThreadLocalMap,该 Map 的 key 是 ThreadLocal 对象本身,value 是 ThreadLocal 定义时枚举类型 T 在该线程上的副本值。也就是说,我们可以定义多个 ThreadLocal,每个 ThreadLocal 对应一类副本值。
public class ThreadLocal<T>
这块可能和传统预测不同,传统我们可能认为 ThreadLocal 本身就是一块公共内存,key 是线程本身,value 是对应副本值。这样每个线程都能访问自己的副本值,但它包含以下问题:
- 所有线程不但要从主内存同步自己的数据,还要存储别的线程的数据,内存消耗大
- 线程之间不私有,理论上仍可以访问到其他线程的数据
所以 TheadLocal 设计反其道而行,Map 本身保存在 Thread 对象中,保证线程私有,再通过 ThreadLocal 对象本身作为 key 去区分不同类型副本,这样每个线程只包含自己副本的值,既节省了空间,也保证了线程私有。关于 ThreadLocalMap 源码,我们下篇给出
根据前面的介绍,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();
}
public void closeConnection() {
Connection c = connectionHolder.get();
c.close();
connectionHolder.remove();
}