對于ThreadLocal這個類我相信多數(shù)java攻城獅都不陌生冠摄,它是java多線程中的一個重要的類纵搁,在面試時經(jīng)常被問到。不知道大家有沒有和我一樣的感覺佃迄,雖然大概知道這個類的含義但對其實現(xiàn)機制以及什么時候該使用這個類似乎總是理解的不太深刻。如果你和我有同樣的困惑贵少,就一起來看看這篇文章呵俏。
1.ThreadLocal是什么
看看ThreadLocal源碼對類的注釋怎么說
This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID)
這段話主要告訴我們四個信息:
- ThreadLocal可以為線程提供專屬本線程的變量
- 變量通過ThreadLocal的get、set方法訪問
- 線程間變量相互獨立
- ThreadLocal對象一般定義為一個私有靜態(tài)變量
OK, 通過這幾個點我們知道了ThreadLocal是用來做什么的滔灶。ThreadLocal為我們提供了一種將多線程共享變量變?yōu)榫€程獨享變量的一種機制普碎,通過ThreadLocal可以將共享變量變?yōu)閷俦揪€程的獨占變量,線程之間可以自由操作ThreadLocal變量录平,互不影響麻车。我們知道了ThreadLocal能干什么,我們再來看看它是怎么實現(xiàn)的斗这。
2.ThreadLocal實現(xiàn)原理
ThreadLocal中最常用的API就是set和get了动猬,set用于給ThreadLocal對象設(shè)置值,get用于從ThreadLocal對象中取值涝影。
private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
public static void main(String[] args) {
if (connectionThreadLocal.get() == null){
connectionThreadLocal.set(ConnectionUtil.getConnection());
}
Thread thread = new Thread(new ConnectionTask(connectionThreadLocal));
thread.start();
try {
//等子線程執(zhí)行結(jié)束后枣察,再繼續(xù)執(zhí)行主線程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Connection connection = connectionThreadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + connection);
}
static class ConnectionTask implements Runnable{
private ThreadLocal<Connection> threadLocal;
public ConnectionTask(ThreadLocal<Connection> threadLocal){
this.threadLocal = threadLocal;
}
public void run() {
Connection connection = threadLocal.get();
if (connection == null){
connection = ConnectionUtil.getConnection();
threadLocal.set(connection);
}
System.out.println(Thread.currentThread().getName() +" : " + connection);
}
}
上面的代碼中在主線程和子線程中分別對同一個ThreadLocal對象執(zhí)行set操作争占,并分別執(zhí)行g(shù)et方法取出ThreadLocal對象中的Connnection對象燃逻。主線程的get操作在子線程全部執(zhí)行完后才繼續(xù)執(zhí)行。大家猜測一下兩個線程中執(zhí)行打印后輸出的是否是同一個Connection對象呢?
可以看到兩個線程中輸出的是兩個connection對象臂痕。這是為什么呢伯襟?要知道我們在兩個線程中操作的可是同一個ThreadLocal對象啊,假如我們將ThreadLocal當做普通的Bean握童,它在主線程中執(zhí)行set操作后在子線程中對該對象再執(zhí)行g(shù)et應(yīng)該取到的是在主線程中set的值姆怪,可是我們看到的結(jié)果并不是如此。我們通過ThreadLocal的get澡绩、set源碼來看看為什么會是這樣的結(jié)果稽揭。
先來看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();
}
在get方法中首先拿到執(zhí)行方法的當前線程t,然后以線程t為參數(shù)調(diào)用了getMap(t)方法返回了一個ThreadLocalMap對象肥卡。如果map對象不為null就從map返回result溪掀,如果為null就返回setInitialValue()。有一點比較費解的是map.getEntry傳入的key是this步鉴,也就是當前ThreadLocal對象揪胃,我們來逐層分析璃哟,一點點揭開ThreadLocal的神秘面紗。關(guān)鍵點在于這個ThreadLocalMap對象喊递,我們來看看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返回的是當前線程中一個threadLocals屬性,線程的threadLocals屬性是啥呢骚勘。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
這是Thread類中的threadLocals屬性铐伴,從注釋上我們可以看到這個threadLocals用來維護屬于這個線程的ThreadLocal對象,而ThreadLocalMap是ThreadLocal的一個內(nèi)部類俏讹。既然ThreadLocalMap是一個Map盛杰,那我們?nèi)タ匆幌滤膋ey、value分別是什么藐石。
/**
* 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;
}
}
這是ThreadLocal的Entry定義即供,可以看到ThreadLocalMap的key是一個ThreadLocal對象,其value是一個Object于微。
看到這里我們應(yīng)該能夠知道的是:執(zhí)行ThreadLocal的get方法會返回屬于當前執(zhí)行線程的變量逗嫡,該變量保存在線程的ThreadLocalMap屬性中。Map以當前ThreadLocal對象為key株依,value是一個Object對象驱证,這個value就是在set到ThreadLocal中的那個值。
再看一下set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到ThreadLocal的set方法同樣是先獲取當前線程恋腕,然后拿到當前線程的ThreadLocalMap引用map抹锄。如果map不為null,就以當前threadLocal對象為key荠藤,T value作為值保存到線程的threadLocals屬性中伙单。否則執(zhí)行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方法很簡單哈肖,就是初始化線程的threadLocals屬性吻育,并將第一個通過ThreadLocal set的值保存到threadLocals中。
看到這里ThreadLocal為線程提供線程本地變量的機制也就很清楚了
- 通過ThreadLocal的set方法設(shè)置的value實際上被保存到了當前線程的ThreadLocalMap對象中淤井,Map以ThreadLocal對象為key布疼,保存的值為value
- 在調(diào)用ThreadLocal的get時,實際上是以當前的ThreadLocal對象為key從當前線程的ThreadLocalMap屬性中取出對應(yīng)的value
這也就解釋了在本節(jié)開頭中那個demo的輸出:同一個ThreadLocal對象币狠,分別在兩個線程中執(zhí)行set方法后游两,再調(diào)用get方法輸出的是兩個不同的對象。因為主線程和子線程中的分別擁有各自的ThreadLocalMap屬性來保存<ThreadLocal this,T value>的鍵值對漩绵,因此兩個線程的中value是互不相干的贱案。
3.什么時候使用ThreadLocal
當訪問共享變量時為了確保并發(fā)安全性,通常需要線程間同步渐行。我們知道線程間同步是非常麻煩且耗費性能的轰坊。一種避免線程間同步的就是不進行線程間數(shù)據(jù)共享铸董,僅在線程內(nèi)訪問數(shù)據(jù)就無需進行線程同步了,這種技術(shù)被稱為線程封閉肴沫。而ThreadLocal就是一種非常好的實現(xiàn)線程封閉的技術(shù)粟害。
當我們不希望線程間共享變量時,或者說線程完全沒有必要進行變量共享時就可以使用ThreadLocal對象將變量設(shè)置為線程獨享的變量颤芬。
例如悲幅,在單線程應(yīng)用程序中可能會維持一個全局的數(shù)據(jù)庫連接,并在程序啟動的時候初始化這個連接對象站蝠,從而避免在調(diào)用每個方法時都初始化都要傳遞一個Connection對象汰具。但是在多線程中Connection對象是共享的,沒有線程同步的情況下這是不安全的菱魔×衾螅可能一個線程正在使用共享的Connection對象,而另一個線程就將Connection對象close了澜倦。利用ThreadLocal則可以為每個線程都初始化一個Connection對象聚蝶,每個線程獨立操作屬于本線程的Connection,互不影響藻治。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
@Override
protected Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection(){
return connectionHolder.get();
}
最后碘勉,需要注意的是在使用ThreadLocal時,一般將ThreadLocal設(shè)置私有靜態(tài)變量桩卵,get前先set或者重寫initialValue方法验靡。如果有多個對象需要set到ThreadLocal中,就初始化多個ThreadLocal變量雏节。
好了胜嗓,ThreadLocal的介紹就到這里。
你的關(guān)注是我持續(xù)更新的動力矾屯!