1.ThreadLocal 是什么痰驱?
在JDK 1.2的版本中就提供java.lang.ThreadLocal证芭,ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序担映。
ThreadLocal并不是一個(gè)Thread废士,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些蝇完。
在JDK5.0中官硝,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>四敞。API方法也相應(yīng)進(jìn)行了調(diào)整,新版本的API方法分別是void set(T value)拔妥、T get()以及T initialValue()忿危。
* 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)
1.1.ThreadLocal 的作用?
ThreadLocal是解決線程安全問(wèn)題一個(gè)很好的思路没龙,它通過(guò)為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問(wèn)的沖突問(wèn)題铺厨。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單硬纤,更方便解滓,且結(jié)果程序擁有更高的并發(fā)性。
1.1.2.ThreadLocal的應(yīng)用場(chǎng)景筝家?
在Java的多線程編程中洼裤,為保證多個(gè)線程對(duì)共享變量的安全訪問(wèn),通常會(huì)使用synchronized來(lái)保證同一時(shí)刻只有一個(gè)線程對(duì)共享變量進(jìn)行操作溪王。這種情況下可以將類變量放到ThreadLocal類型的對(duì)象中腮鞍,使變量在每個(gè)線程中都有獨(dú)立拷貝,不會(huì)出現(xiàn)一個(gè)線程讀取變量時(shí)而被另一個(gè)線程修改的現(xiàn)象莹菱。最常見的ThreadLocal使用場(chǎng)景為用來(lái)解決數(shù)據(jù)庫(kù)連接移国、Session管理等。在下面會(huì)例舉幾個(gè)場(chǎng)景道伟。
2.Android源碼中也可以看到ThreadLocal的身影
這里以andorid 源碼的Handler為例子迹缀。看看Handker是怎么用ThreadLocal的。Handler就必須獲取當(dāng)前線程的 Looper 對(duì)象祝懂,而每一個(gè)線程的 Looper 是不一致的票摇。因?yàn)槊恳粋€(gè)線程都會(huì)有一個(gè) Looper 對(duì)象,因此使用 ThradLocal 去保存和獲取當(dāng)前線程的 Looper 就可以達(dá)到這個(gè)的效果嫂易。
2.1. Looper 內(nèi)部的關(guān)于在 ThreadLocal 中存儲(chǔ) Looper 和 獲取 Looper 的源碼兄朋。
//Looper.prepare();
?
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//將創(chuàng)建的 Looper 對(duì)象保存到 sThreadLocal 中。
sThreadLocal.set(new Looper(quitAllowed));
}
?
?
//從 ThreadLocal 取出 Looper 對(duì)象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
3.ThreadLocal的內(nèi)部原理
我們從源碼中了解ThreadLocal的原理怜械,下面來(lái)看一下具體ThreadLocal是如何實(shí)現(xiàn)的颅和。
ThreadLocal類中提供了幾個(gè)方法:
1.public T get() { }
2.public void set(T value) { }
3.public void remove() { }
4.protected T initialValue(){ }
get()方法是用來(lái)獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來(lái)設(shè)置當(dāng)前線程中變量的副本缕允,remove()用來(lái)移除當(dāng)前線程中變量的副本峡扩,initialValue()是一個(gè)protected方法,一般是用來(lái)在使用時(shí)進(jìn)行重寫的障本,它是一個(gè)延遲加載方法教届,下面會(huì)詳細(xì)說(shuō)明。
3.1.先看下get方法源碼的實(shí)現(xiàn)
* 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();
}
?
/**
* 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;
}
第一句是取得當(dāng)前線程驾霜,然后通過(guò)getMap(t)方法獲取到一個(gè)map案训,map的類型為ThreadLocalMap。然后接著下面獲取到<key,value>鍵值對(duì)粪糙,注意這里獲取鍵值對(duì)傳進(jìn)去的是 this强霎,而不是當(dāng)前線程t。 如果獲取成功蓉冈,則返回value值城舞。如果map為空,則調(diào)用setInitialValue方法返回value寞酿。
看看getMap(t)做了些什么
* 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中家夺,是調(diào)用當(dāng)期線程t,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals伐弹。 那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么?繼續(xù)查看源碼
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
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;
}
}
?
//省略....
}
實(shí)際上就是一個(gè)ThreadLocalMap拉馋,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn)惨好。
再看setInitialValue()方法
setInitialValue()很容易理解椅邓,就是如果map不為空,就設(shè)置鍵值對(duì)昧狮,為空景馁,再創(chuàng)建Map,看一下createMap的實(shí)現(xiàn)逗鸣。
* 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);
}
如果使用ThreadLocal時(shí)合住,先進(jìn)行g(shù)et之前绰精,必須先set,否則會(huì)報(bào)空指針異常
public class ThreadLocalExsample {
ThreadLocal<Long> longLocal = new ThreadLocal<>();
public void set() {
longLocal.set(Thread.currentThread().getId());
}
public long getLong() {
return longLocal.get();
}
public static void main(String[] args) {
ThreadLocalExsample test = new ThreadLocalExsample();
//注意:沒(méi)有set之前透葛,直接get笨使,報(bào)null異常了
System.out.println("-------threadLocal value-------" + test.getLong());
}
}
ThreadLocal的應(yīng)用場(chǎng)景# 數(shù)據(jù)庫(kù)連接
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
ThreadLocal的應(yīng)用場(chǎng)景# Session管理
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
ThreadLocal的應(yīng)用場(chǎng)景# 多線程
* @Author 安仔夏天很勤奮
* Create Date is 2019/3/21
*
* 描述 Java中的ThreadLocal類允許我們創(chuàng)建只能被同一個(gè)線程讀寫的變量。
* 因此僚害,如果一段代碼含有一個(gè)ThreadLocal變量的引用硫椰,即使兩個(gè)線程同時(shí)執(zhí)行這段代碼,
* 它們也無(wú)法訪問(wèn)到對(duì)方的ThreadLocal變量萨蚕。
*/
public class ThreadLocalExsample {
?
/**
* 創(chuàng)建了一個(gè)MyRunnable實(shí)例靶草,并將該實(shí)例作為參數(shù)傳遞給兩個(gè)線程。兩個(gè)線程分別執(zhí)行run()方法岳遥,
* 并且都在ThreadLocal實(shí)例上保存了不同的值奕翔。如果它們?cè)L問(wèn)的不是ThreadLocal對(duì)象并且調(diào)用的set()方法被同步了,
* 則第二個(gè)線程會(huì)覆蓋掉第一個(gè)線程設(shè)置的值浩蓉。但是派继,由于它們?cè)L問(wèn)的是一個(gè)ThreadLocal對(duì)象,
* 因此這兩個(gè)線程都無(wú)法看到對(duì)方保存的值捻艳。也就是說(shuō)驾窟,它們存取的是兩個(gè)不同的值。
*/
public static class MyRunnable implements Runnable {
/**
* 例化了一個(gè)ThreadLocal對(duì)象认轨。我們只需要實(shí)例化對(duì)象一次绅络,并且也不需要知道它是被哪個(gè)線程實(shí)例化。
* 雖然所有的線程都能訪問(wèn)到這個(gè)ThreadLocal實(shí)例好渠,但是每個(gè)線程卻只能訪問(wèn)到自己通過(guò)調(diào)用ThreadLocal的
* set()方法設(shè)置的值昨稼。即使是兩個(gè)不同的線程在同一個(gè)ThreadLocal對(duì)象上設(shè)置了不同的值节视,
* 他們?nèi)匀粺o(wú)法訪問(wèn)到對(duì)方的值拳锚。
*/
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
//一旦創(chuàng)建了一個(gè)ThreadLocal變量,你可以通過(guò)如下代碼設(shè)置某個(gè)需要保存的值
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//可以通過(guò)下面方法讀取保存在ThreadLocal變量中的值
System.out.println("-------threadLocal value-------"+threadLocal.get());
}
}
?
public static void main(String[] args) {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
}
}
?
運(yùn)行結(jié)果
-------threadLocal value-------38
-------threadLocal value-------88
得出結(jié)論
ThreadLocal 中 set 和 get 操作的都是對(duì)應(yīng)線程的 table數(shù)組寻行,因此在不同的線程中訪問(wèn)同一個(gè) ThreadLocal 對(duì)象的 set 和 get 進(jìn)行存取數(shù)據(jù)是不會(huì)相互干擾的霍掺。
總結(jié)
在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來(lái)存儲(chǔ)實(shí)際的變量副本的拌蜘,鍵值為當(dāng)前ThreadLocal變量杆烁,value為變量副本(即T類型的變量)。 初始時(shí)简卧,在Thread里面兔魂,threadLocals為空,當(dāng)通過(guò)ThreadLocal變量調(diào)用get()方法或者set()方法举娩,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化析校,并且以當(dāng)前ThreadLocal變量為鍵值构罗,以ThreadLocal要保存的副本變量為value,存到threadLocals智玻。 然后在當(dāng)前線程里面遂唧,如果要使用副本變量,就可以通過(guò)get方法在threadLocals里面查找吊奢。
實(shí)際的通過(guò)ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的盖彭;
為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量页滚,就像上面代碼中的longLocal和stringLocal召边;
在進(jìn)行g(shù)et之前,必須先set逻谦,否則會(huì)報(bào)空指針異常映屋;如果想在get之前不需要調(diào)用set就能正常訪問(wèn)的話,必須重寫initialValue()方法仲闽。 因?yàn)樵谏厦娴拇a分析過(guò)程中马胧,我們發(fā)現(xiàn)如果沒(méi)有先set的話,即在map中查找不到對(duì)應(yīng)的存儲(chǔ)滋将,則會(huì)通過(guò)調(diào)用setInitialValue方法返回i邻悬,而在setInitialValue方法中,有一個(gè)語(yǔ)句是T value = initialValue()随闽, 而默認(rèn)情況下父丰,initialValue方法返回的是null。