java基礎解析系列(七)---ThreadLocal原理分析

分類:IT技術 時間:2017-09-28

Java基礎解析系列(七)---ThreadLocal原理分析

目錄

  • java基礎解析系列(一)---String、StringBuffer、StringBuilder
  • java基礎解析系列(二)---Integer緩存及裝箱拆箱
  • java基礎解析系列(三)---HashMap原理
  • java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現
  • java基礎解析系列(五)---HashMap並發下的問題以及HashTable和CurrentHashMap的區別
  • java基礎解析系列(六)---註解原理及使用
  • 這是我的博客目錄,歡迎閱讀

作用

  • 與同步機制區分開來,同步機制是為了解決在共享情況下並發導致的問題。而ThreadLocal是避免了共享
  • 在多線程情況下,為了避免共享,我們可以采用多線程多實例的方式,也可以使用ThreadLocal來避免共享沖突

什麽是ThreadLocal

  • ThreadLocal提供了線程本地變量,它可以保證訪問到的變量屬於當前線程,每個線程都保存有一個變量副本,每個線程的變量都不同。ThreadLocal相當於提供了一種線程隔離,將變量與線程相綁定

實驗

public class T {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    public void set() {
        longLocal.set(Thread.currentThread().getId());
    }
    public long getLong() {
        return longLocal.get();
    }
    public static void main(String[] args) throws InterruptedException {
        final T test = new T();
        test.set();
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                system.out.println("線程一:"+test.getLong());
            };
        };
        thread1.start();
        thread1.join();
        System.out.println("main線程:"+test.getLong());
        System.out.println("沒有發生值的覆蓋,兩個線程保存的值是不同的");
    }
}
  • 輸出: 線程一:11 main線程:1 沒有發生值的覆蓋,兩個線程保存的值是不同的
  • 證明ThreadLocal確實為變量在每個線程中都創建了一個副本

Thread的成員

ThreadLocal.ThreadLocalMap threadLocals = null;
  • Thread有一個ThreadLocalMap成員

ThreadLocal的set方法

179    public void set(T value) {
180        Thread t = Thread.currentThread();
181        ThreadLocalMap map = getMap(t);
182        if (map != null)
183            map.set(this, value);
184        else
185            createMap(t, value);
186    }
  • 181行通過當前線程獲取ThreadLocalMap
212    ThreadLocalMap getMap(Thread t) {
213        return t.threadLocals;
214    }
  • 185行如果當前線程的成員threadLocals還是空的,創建一個map
224    void createMap(Thread t, T firstValue) {
225        t.threadLocals = new ThreadLocalMap(this, firstValue);
226    }
  • 將當前的ThreadLocal對象和value作為參數
328        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
329            table = new Entry[INITIAL_CAPACITY];
330            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
331            table[i] = new Entry(firstKey, firstValue);
332            size = 1;
333            setThreshold(INITIAL_CAPACITY);
334        }

  • 通過ThreadLocalMap的構造方法可以看到,該方法創建一個Entry數組,然後通過傳入的key(當前ThreadLocal對象)計算在數組中的下標,然後將Entry放入數組。
  • Thread的ThreadLocalMap存放的Entry,鍵是不同的ThreadLoacal對象,也就是說一個線程綁定多個ThreadLocal對象
  • 那麽也就是說,ThreadLocal設置值的時候,這個值是存放在當前線程的一個map(這個map存放了多個ThreadLocal對象)裏面,因此,不同線程之間即避免了共享

ThreadLocalMap的set方法

416        private void set(ThreadLocal key, Object value) {
417
418            // We don't use a fast path as with get() because it is at
419            // least as common to use set() to create new entries as
420            // it is to replace existing ones, in which case, a fast
421            // path would fail more often than not.
422
423            Entry[] tab = table;
424            int len = tab.length;
425            int i = key.threadLocalHashCode & (len-1);
426
427            for (Entry e = tab[i];
428                 e != null;
429                 e = tab[i = nextIndex(i, len)]) {
430                ThreadLocal k = e.get();
431
432                if (k == key) {
433                    e.value = http://www.cnblogs.com/-new/p/value;
434                    return;
435                }
436
437                if (k == null) {
438                    replaceStaleEntry(key, value, i);
439                    return;
440                }
441            }
442
443            tab[i] = new Entry(key, value);
444            int sz = ++size;
445            if (!cleanSomeSlots(i, sz) && sz >= threshold)
446                rehash();
447        }
  • 分析這段代碼,他解決hash沖突的辦法不同與hashmap使用鏈表來解決沖突問題。通過計算key的hashcode獲取數組中的下標後,然後進入427行,432判斷要放入的鍵是否和該下標中原來的鍵相同,是的話進行值的覆蓋。如果為空的,放入該Entry。如果不同的話且不為空,看當前下標+1的位置,同樣進入循環。依次執行下去。

ThreadLocal的get方法

142    public T get() {
143        Thread t = Thread.currentThread();
144        ThreadLocalMap map = getMap(t);
145        if (map != null) {
146            ThreadLocalMap.Entry e = map.getEntry(this);
147            if (e != null)
148                return (T)e.value;
149        }
150        return setInitialValue();
151    }
  • 可以發現,執行get方法的時候,也是先獲取當前線程,然後獲得該線程的一個ThreadLocalMap成員,通過這個map和和當前的ThreadLocal對象作為鍵,來獲取value

內存泄露

271        static class Entry extends WeakReference<ThreadLocal> {
  • 如果ThreadLocal不使用弱引用(這篇文章有介紹),那麽當ThradLocal th=null時候,因為ThreadLocalMap仍然有th的強引用,所以並不能回收。而如果key使用弱引用的時候,th為null的時候,下次回收的時候就會將這個key回收
  • 但是有一個問題,雖然這個key可以被回收,但是這個value仍然有強引用,並不能回收。如果當前線程不結束,並且不調用set/get/remove方法(這些方法會對key為null的entry進行釋放),這片內存會被一直占用。這就是內存泄露的原因
  • 因此在用完ThreadLocal的時候,記得執行remove方法,避免內存泄露

簡單總結

  • 同一個ThreadLocal對象,不同的線程,不同的ThreadLocal對象的值

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鐘、半秒鐘,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步麽?那就【關註】我吧。


Tags: ThreadLocal 線程 解析 基礎 系列 變量

文章來源:


ads
ads

相關文章
ads

相關文章

ad