一.什么是ThreadLocal
二.深入解析ThreadLocal
三.巧用ThreadLocal
- ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
- 深入源码一探究竟
a.首先了解一下几个通用的方法public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,在不调用set()方法的情况下,靠此方法初始化副本的变量
b.首先我们先来看一下get方法的实现/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); }
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。如果map不为空,则返回value值。否则则调用setInitialValue方法返回value。
b.再看一下getMap方法中做了什么:/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。
c.那么我们继续取Thread类中取看一下成员变量threadLocals是什么:/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类.
d.我们继续取看ThreadLocalMap的实现:
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //此处省略部分。。。。。。。。。。 }
可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。
e.然后再继续看setInitialValue方法的具体实现:/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
T value = initialValue();一般我们会重写此方法,走到这里会调用我们自己写的initialValue();方法,该方法用于给线程副本初始化,在接着代码就是,如果map不为空,就设置键值对,如果为空,再创建Map.
f.看一下createMap的实现:/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
createMap()就是为接上面map为空的情况下,来为ThreadLocalMap初始化的
g.至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的,再总结一下,首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。 -
巧用threadLocals
利用threadLocals我们可以将数据库连接对象,或者一些上下文对象,保存在当前每个线程副本中。这样我们就不用在将这个变量用于参数传递,并且在每个线程中都可以获取,并且互不影响的,大大简化了操作,下面就医数据库连接对象为例,贴出代码实现。
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * 等效 JdbcUtils,用于提供获得连接工具类 */ public class C3P0Utils { //连接池 private static ComboPooledDataSource dataSource = new ComboPooledDataSource("test"); //提供ThreadLocal用于为不同线程缓存连接 private static ThreadLocal<Connection> local = new ThreadLocal<Connection>(); /** * 获得数据源(连接池) * @return */ public static DataSource getDataSource(){ return dataSource; } /** * 获得连接 * @return * @throws SQLException */ public static Connection getConnection() throws SQLException{ //1 从本地线程变量获得 Connection conn = local.get(); //2 如果没有,从连接池获得,并添加到ThreadLocal中 if(conn == null){ //将从连接池中获得连接 conn = dataSource.getConnection(); //需保持本地 local.set(conn); } //3 返回连接 return conn; } }