ThreadLocal 简述
文章目录
前言
之前面试又被问过关于 ThreadLocal 的使用以及实现原理,回答的不是很好,在这里会对 ThreadLocal进行分析,介绍,以及总结性概述
场景
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。如
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
上面是我们最初的数据库连接的一个设计,上面主要有三个问题:
- 线程同步问题,这种做法显然是不符合并发的,会多次生产 connect 对象
- 会有一个线程在使用关闭的方法,另一个线程还在使用这个连接
- 一个线程在使用连接对象的时候,其他的对象只能够等待,影响执行效率
针对上面的两个问题,在下面进行了改进,使用了今天的主角ThreadLocal
,为每个线程都创建一个私有的连接对象
private static ThreadLocal < Connection > connectionHolder = new ThreadLocal < Connection > ();
public static Connection getConnection() throws InfrastructureException{
Connection connection;
try {
if ((connection = connectionHolder.get()) == null) {
connection = DriverManager.getConnection(DB_URL);
connectionHolder.set(connection);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return connection;
}
public static void closeConnection() {
Connection connection;
if((connection = connectionHolder.get()) != null)
connection.close();
}
实现原理
以上是我们使用ThreadLocal
实现数据库连接的一种场景,接下来我们先看下ThreadLocal
的内部实现原理
ThreadLocal
先看一下set()
方法
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建一个ThreadLocalMap实例,并将实例的引用保存到当前线程的属性中
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
以上就是set
方法的全部内容
- 首先就是获取到当前的线程
t
- 获取当前线程
t
的ThreadLocalMap
- 判断
ThreadLocalMap
是否为空,返回存放了value
值的ThreadLocalMap
我们继续看下get()
方法
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();
}
以上就是get()
方法的内容,我们简单看下
- 获取当前线程的
ThreadLocalMap
;为null
,初始化ThreadLocalMap
后返回; - 不为空,将
ThreadLocal
对象作为key
,从 map 中获取到value
值
ThreadLocal$ ThreadLocalMap
从上面ThreadLocal
的get()
和set
方法中,我们看到存储的容器是ThreadLocalMap
我们接下来看下关于ThreadLocalMap
的代码
//ThreadLocalMap 是 ThreadLocal 的一个内部类
static class ThreadLocalMap {
//内部的 Entry 则是用来保存我们需要保存的变量容器
//这里可以看到 Entry 继承了WeakReference,详见页尾附录
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//key 就是当前 ThreadLocal 的对象
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//firstKey就是当前 ThreadLocal的对象引用,
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
...
}
private void remove(ThreadLocal<?> key) {
....
}
...
}
以上是 JDK 中的源代码部分,我们用一张图来表示一下
Thread
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
每个线程Thread内部有一个ThreadLocal.ThreadLocalMap
类型的成员变量threadLocals
,这个threadLocals
就是用来存储实际的变量副本的,键值为当前ThreadLocal
变量,value为变量副本(即T类型的变量)。
关系引用图
总结
Thread
实例会维护一个ThreadLocalMap
的引用ThreadLocalMap
是ThreadLocal
的内部类,在其内部有一个Entry
来进行存储变量ThreadLocal#set()
中,我们将当前ThreadLocal
的引用作为 key,传入的 obj 作为 value,保存进ThreadLocalMap$Entry
中ThreadLocal
本身不存储值,仅仅作为一个key
来让线程从ThreadLocalMap
获取value
附录
为何Entry
使用弱引用?
在上面ThreadLocalMap
中,我们看到Entry
是继承了WeakReference
,那么问题来了,为什么要继承?不继承能不能实现现有的功能。
未完待续。。。。。