天天看點

ThreadLocal剖析

通常情況下,我們建立的變量都是可用被任何一個線程通路并修改的。這就會導緻一系列的問題,比如說兩個人去打工,如果兩個人公用一個賬戶存錢,那麼後續必然會産生一些争執,最好的解決方法就是給他們兩個人每個人都配置設定一個屬于自己的賬戶,這樣講避免了争執。

這個推廣到線程上就是使用ThreadLoad來避免線程之間的競争。

ThreadLocal原理,使用注意點,應用場景有哪些?

回答四個主要點:

  • ThreadLocal是什麼?
  • ThreadLocal原理
  • ThreadLocal使用注意點
  • ThreadLocal的應用場景

ThreadLocal是什麼?

ThreadLocal,即線程本地變量。如果你建立了一個ThreadLocal變量,那麼通路這個變量的每個線程都會有這個變量的一個本地拷貝,多個線程操作這個變量的時候,實際是操作自己本地記憶體裡面的變量,進而起到線程隔離的作用,避免了線程安全問題。

//建立一個ThreadLocal變量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
           

ThreadLocal原理

ThreadLocal記憶體結構圖:

ThreadLocal剖析

由結構圖是可以看出:

  • Thread對象中持有一個ThreadLocal.ThreadLocalMap的成員變量。
  • ThreadLocalMap内部維護了Entry數組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。

對照着幾段關鍵源碼來看,更容易了解一點

public class Thread implements Runnable {
 ......
//與此線程有關的ThreadLocal值。由ThreadLocal類維護
ThreadLocal.ThreadLocalMap threadLocals = null;

//與此線程有關的InheritableThreadLocal值。由InheritableThreadLocal類維護
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
 ......
}
           

從Thread類源碼可以看出Thread類中有一個threadLocals和一個inheritableThreadLocals變量,他們都是ThreadLocalMap類型的變量。

ThreadLocal中的關鍵方法set()和get()

public void set(T value) {
        Thread t = Thread.currentThread(); //擷取目前線程t
        ThreadLocalMap map = getMap(t);  //根據目前線程擷取到ThreadLocalMap
        if (map != null)
            map.set(this, value); //K,V設定到ThreadLocalMap中
        else
            createMap(t, value); //建立一個新的ThreadLocalMap
    }
    public T get() {
        Thread t = Thread.currentThread();//擷取目前線程t
        ThreadLocalMap map = getMap(t);//根據目前線程擷取到ThreadLocalMap
        if (map != null) {
            //由this(即ThreadLoca對象)得到對應的Value,即ThreadLocal的泛型值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value; 
                return result;
            }
        }
        return setInitialValue();
    }
           

通過源碼我們得知,最終的變量是存放在了目前線程的ThreadLocalMap中,并不是存在Threadlocal上。

每個Thread中都具備一個ThreadLocalMap,而ThreadLocal可以存儲以ThreadLocal為key,Object對象為value的鍵值對。

ThreadLocalMap的Entry數組

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}
           

是以怎麼回答**「ThreadLocal的實作原理」**?如下,最好是能結合以上結構圖一起說明

  • Thread類有一個類型為ThreadLocal.ThreadLocalMap的執行個體變量threadLocals,即每個線程都有一個屬于自己的ThreadLocalMap。
  • ThreadLocalMap内部維護着Entry數組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
  • 每個線程在往ThreadLocal裡設定值的時候,都是往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,進而實作了線程隔離。

ThreadLocal 記憶體洩露問題

先看看一下的TreadLocal的引用示意圖:

ThreadLocal剖析

ThreadLocalMap中使用的 key 為 ThreadLocal 的弱引用,而value為強引用,如下

ThreadLocal剖析
弱引用:隻要垃圾回收機制一運作,不管JVM的記憶體空間是否充足,都會回收該對象占用的記憶體。

弱引用比較容易被回收。是以,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因為ThreadLocalMap生命周期和Thread是一樣的,它這時候如果不被回收,就會出現這種情況:ThreadLocalMap的key沒了,value還在,這就會**「造成了記憶體洩漏問題」。如何「解決記憶體洩漏問題」**?使用完ThreadLocal後,及時調用remove()方法釋放記憶體空間。

ThreadLocal的應用場景

  • 資料庫連接配接池
  • 會話管理中使用

最後

  • 如果覺得看完有收獲,希望能給我點個贊,這将會是我更新的最大動力,感謝各位的支援
  • 歡迎各位關注我的公衆号【java冢狐】,專注于java和計算機基礎知識,保證讓你看完有所收獲,不信你打我
  • 如果看完有不同的意見或者建議,歡迎多多評論一起交流。感謝各位的支援以及厚愛。

——我是冢狐,和你一樣熱愛程式設計。

ThreadLocal剖析