ThreadLocal作用、場景、原理

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里面查找。

  1. 實際的通過ThreadLocal創(chuàng)建的副本是存儲在每個線程自己的threadLocals中的;

  2. 為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;

  3. 在進行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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容