今天来学习 Java 中的 ThreadLocal,也叫作本地变量,主要有以下知识点:
基本使用
源码解析
使用场景
概述
ThreadLocal
,很多地方叫做线程本地变量。
ThreadLocal 在每个线程中为变量创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
源码解析
ThreadLocal 类提供的几个方法:
public T get() {}
public void set(T value) {}
public void remove() {}
protected T initialValue() {}
get()
方法是用来获取 ThreadLocal 在当前线程中保存的变量副本.set()
用来设置当前线程中变量的副本.remove()
用来移除当前线程中变量的副本.initialValue()
是一个 protected 方法,一般是用来在使用时进行重写.
首先我们来看一下 ThreadLocal 类是如何为每个线程创建一个变量的副本的。
1. 先看下 get
方法的实现:
public T get() {Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
return setInitialValue();}
第一句是取得当前线程,然后通过 getMap(t)
方法获取到一个 map,map 的类型为 ThreadLocalMap
。
然后接着下面获取到 <key,value>
键值对,注意这里获取键值对传进去的是 this,而不是当前线程 t。
如果获取成功,则返回 value 值。
如果 map 为空,则调用 setInitialValue
方法返回 value。
2. 接着看一下 getMap
方法中做了什么:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
getMap
中,是调用当前线程 t,返回当前线程 t 中的一个成员变量threadLocals
。
3. 那么我们继续取 Thread 类中取看一下成员变量 threadLocals
是什么:
ThreadLocal.ThreadLocalMap threadLocals = null;
实际上就是一个 ThreadLocalMap
, 这个类型是 ThreadLocal 类的一个 内部类 , 我们继续取看ThreadLocalMap
的实现:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {super(k);
value = v;
}
}
}
可以看到 ThreadLocalMap
的 Entry 继承了 WeakReference,并且 使用 ThreadLocal 作为键值。
4. 然后再继续看 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;
}
很容易了解,就是如果 map 不为空,就设置键值对,为空,再创建 Map,看一下 createMap
的实现:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
至此,可能大部分朋友已经明白了 ThreadLocal 是如何为每个线程创建变量的副本的:
首先,在 每个线程 Thread 内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals,这个 threadLocals 就是用来存储实际的变量副本的,key 值为当前 ThreadLocal 变量,value 为变量副本(即 T 类型的变量)。
初始时,在 Thread 里面,threadLocals 为空,当通过 ThreadLocal 变量调用 get()方法或者 set()方法,就会对 Thread 类中的 threadLocals 进行初始化,并且 以当前 ThreadLocal 变量为键值,以 ThreadLocal 要保存的副本变量为 value,存到 threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过 get 方法在 threadLocals 里面查找。
总结
- 每个 Thread 维护着一个 ThreadLocalMap 的引用
- ThreadLocalMap 是 ThreadLocal 的内部类,用 Entry 来进行存储
- 调用 ThreadLocal 的 set()方法时,实际上就是往 ThreadLocalMap 设置值,key 是 ThreadLocal 对象,值是传递进来的对象
- 调用 ThreadLocal 的 get()方法时,实际上就是往 ThreadLocalMap 获取值,key 是 ThreadLocal 对象
- ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value
使用场景
最常见的 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();
}
};
有关多线程的知识暂时先到这里,后面有时间再补充!