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

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

目錄

做用

  • 與同步機制區分開來,同步機制是爲了解決在共享狀況下併發致使的問題。而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 = 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/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。若是以爲還有幫助的話,能夠點一下右下角的【推薦】,但願可以持續的爲你們帶來好的技術文章!想跟我一塊兒進步麼?那就【關注】我吧。html

相關文章
相關標籤/搜索