今天来学习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里面查找。
最常见的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(); } };
有关多线程的知识暂时先到这里,后面有时间再补充!