ThreadLocal 的作用和實(shí)現(xiàn)原理

     ThreadLocal  類用來(lái)提供線程內(nèi)部的局部變量,并且這些變量依靠線程獨(dú)立存在.
     可以在多個(gè)線程中互不干擾的進(jìn)行存儲(chǔ)數(shù)據(jù)和修改數(shù)據(jù)迎献,通過set,get 和remove方法瞎访, 
     每個(gè)線程都是獨(dú)立的操作.

     里面的原理是:在不同的線程中訪問同一個(gè)對(duì)象,獲取到的值是不一樣的吁恍,因?yàn)閮?nèi)部會(huì) 
     從各種的線程中取出一個(gè)數(shù)組扒秸。 通過對(duì)應(yīng)的下標(biāo),查找對(duì)應(yīng)的Value值

下面看個(gè)簡(jiǎn)單的列子:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        
        final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        threadLocal.set("123");
        Log.e("Charles2", "ss1==" + threadLocal.get());

        new Thread(new Runnable() {
            @Override
            public void run() {
                String s = threadLocal.get();
                Log.e("Charles2", "ss2=" + s);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("234");
                String s = threadLocal.get();
                Log.e("Charles2", "ss3==" + s);
            }
        }).start();
    }

打印的結(jié)果如下 :

07-18 15:58:14.312 21113-21113/? E/Charles2: ss1==123
07-18 15:58:14.326 21113-21140/? E/Charles2: ss2=null
07-18 15:58:14.327 21113-21141/? E/Charles2: ss3==234
 大家都知道這三行打印都在不同的線程冀瓦,線程1是在主線程伴奥,線程2 和 線程3 分別在不各 
 自線程中,但是由于線程2沒有給它設(shè)置值所以取出來(lái)的是null.其它2個(gè)線程的內(nèi)部變量 
 都是有值.

二翼闽、實(shí)現(xiàn)原理
ThreadLocal的set()分析

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如上面的代碼看拾徙,得到map對(duì)象之后,用的this作為key肄程,this在這里代表的是當(dāng)前線程的ThreadLocal對(duì)象锣吼。 另外就是第二句根據(jù)getMap獲取一個(gè)ThreadLocalMap选浑,其中g(shù)etMap中傳入了參數(shù)t(當(dāng)前線程對(duì)象),這樣就能夠獲取每個(gè)線程的ThreadLocal了玄叠。 繼續(xù)跟進(jìn)到ThreadLocalMap中查看set方法:

(2)1.ThreadLocalMap
ThreadLocalMap是ThreadLocal的一個(gè)內(nèi)部類古徒,在分析其set方法之前,查看一下其類結(jié)構(gòu)和成員變量读恃。

    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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

然后看下ThreadLocal的構(gòu)造方法

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

構(gòu)造函數(shù)的第一個(gè)參數(shù)就是本ThreadLocal實(shí)例(this)隧膘,第二個(gè)參數(shù)就是要保存的線程本地變量。構(gòu)造函數(shù)首先創(chuàng)建一個(gè)長(zhǎng)度為16的Entry數(shù)組寺惫,然后計(jì)算出firstKey對(duì)應(yīng)的哈希值疹吃,然后存儲(chǔ)到table中,并設(shè)置size和threshold西雀。

注意一個(gè)細(xì)節(jié)萨驶,計(jì)算hash的時(shí)候里面采用了hashCode & (size - 1)的算法,這相當(dāng)于取模運(yùn)算hashCode % size的一個(gè)更高效的實(shí)現(xiàn)(和HashMap中的思路相同)艇肴。正是因?yàn)檫@種算法腔呜,我們要求size必須是2的指數(shù),因?yàn)檫@可以使得hash發(fā)生沖突的次數(shù)減小再悼。

ThreadLocalMap#set
ThreadLocal中put函數(shù)最終調(diào)用了ThreadLocalMap中的set函數(shù)核畴,跟進(jìn)去看一看:
···
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

        for (Entry e = tab[i];
             e != null;
              // 沖突了
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal k = e.get();

            if (k == key) {
                e.value = value;
                return;
            }

            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

···
在上述代碼中如果Entry在存放過程中沖突了,調(diào)用nextIndex來(lái)處理冲九,如下所示谤草。是否還記得hashmap中對(duì)待沖突的處理?這里好像是另一種套路:只要i的數(shù)值小于len莺奸,就加1取值丑孩,官方術(shù)語(yǔ)稱為:線性探測(cè)法。

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

以上步驟ok了之后憾筏,再次關(guān)注一下源碼中的cleanSomeSlots嚎杨,該函數(shù)主要的作用就是清理無(wú)用的entry,具體細(xì)節(jié)就不扣了:

        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

下面來(lái)看ThreadLocal#get

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

如果map不會(huì)空氧腰,就根據(jù)當(dāng)前線程去查存儲(chǔ)的變量。
如果map為null刨肃,就返回setInitialValue()這個(gè)方法古拴,跟進(jìn)這個(gè)方法看一下:

    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;
    }

最后返回的是value,而value來(lái)自initialValue(),進(jìn)入這個(gè)源碼中查看:

    protected T initialValue() {
        return null;
    }

原來(lái)如此真友,如果不設(shè)置ThreadLocal的數(shù)值黄痪,默認(rèn)就是null,來(lái)自于此盔然。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桅打,一起剝皮案震驚了整個(gè)濱河市是嗜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挺尾,老刑警劉巖鹅搪,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遭铺,居然都是意外死亡丽柿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門魂挂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甫题,“玉大人,你說我怎么就攤上這事涂召∽狗牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵果正,是天一觀的道長(zhǎng)麻顶。 經(jīng)常有香客問我,道長(zhǎng)舱卡,這世上最難降的妖魔是什么辅肾? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮轮锥,結(jié)果婚禮上矫钓,老公的妹妹穿的比我還像新娘。我一直安慰自己舍杜,他們只是感情好新娜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著既绩,像睡著了一般概龄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饲握,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天私杜,我揣著相機(jī)與錄音,去河邊找鬼救欧。 笑死衰粹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笆怠。 我是一名探鬼主播铝耻,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蹬刷!你這毒婦竟也來(lái)了瓢捉?” 一聲冷哼從身側(cè)響起频丘,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泡态,沒想到半個(gè)月后搂漠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兽赁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年状答,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刀崖。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惊科,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亮钦,到底是詐尸還是另有隱情馆截,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布蜂莉,位于F島的核電站蜡娶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏映穗。R本人自食惡果不足惜窖张,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚁滋。 院中可真熱鬧宿接,春花似錦、人聲如沸辕录。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)走诞。三九已至副女,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚣旱,已是汗流浹背碑幅。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姻锁,地道東北人枕赵。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像位隶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子开皿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容