1.ThreadLocal 是什么?
在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序。
ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。
在JDK5.0中,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>。API方法也相應(yīng)進行了調(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是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結(jié)果程序擁有更高的并發(fā)性。
1.1.2.ThreadLocal的應(yīng)用場景?
在Java的多線程編程中,為保證多個線程對共享變量的安全訪問,通常會使用synchronized來保證同一時刻只有一個線程對共享變量進行操作。這種情況下可以將類變量放到ThreadLocal類型的對象中,使變量在每個線程中都有獨立拷貝,不會出現(xiàn)一個線程讀取變量時而被另一個線程修改的現(xiàn)象。最常見的ThreadLocal使用場景為用來解決數(shù)據(jù)庫連接、Session管理等。在下面會例舉幾個場景。
2.Android源碼中也可以看到ThreadLocal的身影
這里以andorid 源碼的Handler為例子??纯碒andker是怎么用ThreadLocal的。Handler就必須獲取當(dāng)前線程的 Looper 對象,而每一個線程的 Looper 是不一致的。因為每一個線程都會有一個 Looper 對象,因此使用 ThradLocal 去保存和獲取當(dāng)前線程的 Looper 就可以達到這個的效果。
2.1. Looper 內(nèi)部的關(guān)于在 ThreadLocal 中存儲 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 對象保存到 sThreadLocal 中。
sThreadLocal.set(new Looper(quitAllowed));
}
?
?
//從 ThreadLocal 取出 Looper 對象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
3.ThreadLocal的內(nèi)部原理
我們從源碼中了解ThreadLocal的原理,下面來看一下具體ThreadLocal是如何實現(xiàn)的。
ThreadLocal類中提供了幾個方法:
1.public T get() { }
2.public void set(T value) { }
3.public void remove() { }
4.protected T initialValue(){ }
get()方法是用來獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來設(shè)置當(dāng)前線程中變量的副本,remove()用來移除當(dāng)前線程中變量的副本,initialValue()是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲加載方法,下面會詳細說明。
3.1.先看下get方法源碼的實現(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)前線程,然后通過getMap(t)方法獲取到一個map,map的類型為ThreadLocalMap。然后接著下面獲取到<key,value>鍵值對,注意這里獲取鍵值對傳進去的是 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中的一個成員變量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;
}
}
?
//省略....
}
實際上就是一個ThreadLocalMap,這個類型是ThreadLocal類的一個內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實現(xiàn)。
再看setInitialValue()方法
setInitialValue()很容易理解,就是如果map不為空,就設(shè)置鍵值對,為空,再創(chuàng)建Map,看一下createMap的實現(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時,先進行g(shù)et之前,必須先set,否則會報空指針異常
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();
//注意:沒有set之前,直接get,報null異常了
System.out.println("-------threadLocal value-------" + test.getLong());
}
}
ThreadLocal的應(yīng)用場景# 數(shù)據(jù)庫連接
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
ThreadLocal的應(yī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)用場景# 多線程
* @Author 安仔夏天很勤奮
* Create Date is 2019/3/21
*
* 描述 Java中的ThreadLocal類允許我們創(chuàng)建只能被同一個線程讀寫的變量。
* 因此,如果一段代碼含有一個ThreadLocal變量的引用,即使兩個線程同時執(zhí)行這段代碼,
* 它們也無法訪問到對方的ThreadLocal變量。
*/
public class ThreadLocalExsample {
?
/**
* 創(chuàng)建了一個MyRunnable實例,并將該實例作為參數(shù)傳遞給兩個線程。兩個線程分別執(zhí)行run()方法,
* 并且都在ThreadLocal實例上保存了不同的值。如果它們訪問的不是ThreadLocal對象并且調(diào)用的set()方法被同步了,
* 則第二個線程會覆蓋掉第一個線程設(shè)置的值。但是,由于它們訪問的是一個ThreadLocal對象,
* 因此這兩個線程都無法看到對方保存的值。也就是說,它們存取的是兩個不同的值。
*/
public static class MyRunnable implements Runnable {
/**
* 例化了一個ThreadLocal對象。我們只需要實例化對象一次,并且也不需要知道它是被哪個線程實例化。
* 雖然所有的線程都能訪問到這個ThreadLocal實例,但是每個線程卻只能訪問到自己通過調(diào)用ThreadLocal的
* set()方法設(shè)置的值。即使是兩個不同的線程在同一個ThreadLocal對象上設(shè)置了不同的值,
* 他們?nèi)匀粺o法訪問到對方的值。
*/
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
//一旦創(chuàng)建了一個ThreadLocal變量,你可以通過如下代碼設(shè)置某個需要保存的值
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//可以通過下面方法讀取保存在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();
}
}
?
運行結(jié)果
-------threadLocal value-------38
-------threadLocal value-------88
得出結(jié)論
ThreadLocal 中 set 和 get 操作的都是對應(yīng)線程的 table數(shù)組,因此在不同的線程中訪問同一個 ThreadLocal 對象的 set 和 get 進行存取數(shù)據(jù)是不會相互干擾的。
總結(jié)
在每個線程Thread內(nèi)部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。 初始時,在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。 然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。
實際的通過ThreadLocal創(chuàng)建的副本是存儲在每個線程自己的threadLocals中的;
為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;
在進行g(shù)et之前,必須先set,否則會報空指針異常;如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法。 因為在上面的代碼分析過程中,我們發(fā)現(xiàn)如果沒有先set的話,即在map中查找不到對應(yīng)的存儲,則會通過調(diào)用setInitialValue方法返回i,而在setInitialValue方法中,有一個語句是T value = initialValue(), 而默認情況下,initialValue方法返回的是null。