線程封閉

多線程共享數(shù)據(jù)的時(shí)候东抹,涉及到訪問共享變量介蛉。但是有時(shí)候不需要使用共享變量,將變量封閉在線程中燥透,只供本線程使用沙咏。
在java中通過ThreadLocal或者局部變量來實(shí)現(xiàn)線程封閉。
線程獲得ThreadLocal中的值都是拷貝一個(gè)副本班套,只供本線程中操作肢藐,對原來的值不會有改變。

ThreadLocal源碼分析:

ThreadLocal像是一個(gè)就像一個(gè)工具類吱韭,每一個(gè)線程都有一個(gè)Map可供ThreadLocal處理吆豹,線程可以set和get它。為什么可以實(shí)現(xiàn)呢理盆?原因是每當(dāng)線程調(diào)用的時(shí)候痘煤,我們都可以知道此時(shí)執(zhí)行的線程是哪一個(gè),通過System.currentThread()方法猿规。這就相當(dāng)于一個(gè)key衷快,給我們造成一種視覺錯(cuò)誤就是ThreadLocal好神奇,不知不覺就可以把值傳給線程坎拐。其實(shí)不然,關(guān)鍵就是System.currentThread()可以獲得當(dāng)前線程养匈,當(dāng)我們在線程中調(diào)用ThreadLocal的get()方法時(shí)哼勇,實(shí)際上是訪問了線程自己內(nèi)部保存的那個(gè)map,所以線程和這個(gè)map是一一對應(yīng)的關(guān)系呕乎。
下面就來分析源碼:

  1. Thread類中保存了ThreadLocalMap對象
  2. ThreadLocalMap分析
static class ThreadLocalMap {

      //內(nèi)部是一個(gè)Entry類积担,保存實(shí)際的value
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

       //初始容量
        private static final int INITIAL_CAPACITY = 16;

      //數(shù)組
        private Entry[] table;

   
        private int size = 0;

        
        private int threshold; // Default to 0

       
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

    
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

      
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

       //創(chuàng)建一個(gè)ThreadLocalMap,懶加載機(jī)制猬仁,只有當(dāng)?shù)谝粋€(gè)值插入的時(shí)候創(chuàng)建
        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);
        }

       //復(fù)制父類ThreadLocalMap的值
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

      //獲得通過threadLocal獲得值
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

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

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        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;
        }

        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }

簡單理解就是帝璧,Thread中保存了一個(gè)Map先誉,key是ThreadLocal,value是具體ThreadLocal對應(yīng)的值的烁。線程通過ThreadLocal調(diào)用來獲得本線程中對應(yīng)ThreadLocal對應(yīng)的值褐耳,O了,就這么簡單渴庆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铃芦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子襟雷,更是在濱河造成了極大的恐慌刃滓,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耸弄,死亡現(xiàn)場離奇詭異咧虎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)计呈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門砰诵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人震叮,你說我怎么就攤上這事胧砰。” “怎么了苇瓣?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵尉间,是天一觀的道長。 經(jīng)常有香客問我击罪,道長哲嘲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任媳禁,我火速辦了婚禮眠副,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竣稽。我一直安慰自己囱怕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布毫别。 她就那樣靜靜地躺著娃弓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岛宦。 梳的紋絲不亂的頭發(fā)上台丛,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音砾肺,去河邊找鬼挽霉。 笑死防嗡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侠坎。 我是一名探鬼主播蚁趁,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硅蹦!你這毒婦竟也來了荣德?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤童芹,失蹤者是張志新(化名)和其女友劉穎涮瞻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體假褪,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡署咽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了生音。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宁否。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缀遍,靈堂內(nèi)的尸體忽然破棺而出慕匠,到底是詐尸還是另有隱情,我是刑警寧澤域醇,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布台谊,位于F島的核電站,受9級特大地震影響譬挚,放射性物質(zhì)發(fā)生泄漏锅铅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一减宣、第九天 我趴在偏房一處隱蔽的房頂上張望盐须。 院中可真熱鬧,春花似錦漆腌、人聲如沸贼邓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塑径。三九已至,卻和暖如春悠砚,著一層夾襖步出監(jiān)牢的瞬間晓勇,已是汗流浹背堂飞。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工灌旧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绑咱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓枢泰,卻偏偏與公主長得像描融,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子衡蚂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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

  • 1 線程封閉 多線程訪問共享可變數(shù)據(jù)時(shí)窿克,涉及到線程間數(shù)據(jù)同步的問題。并不是所有時(shí)候毛甲,都要用到共享數(shù)據(jù)年叮,所以線程封閉...
    JavaEdge閱讀 1,487評論 0 7
  • 線程封閉概念 多線程訪問共享數(shù)據(jù)為了安全性通常需要同步,如果僅在單線程內(nèi)訪問數(shù)據(jù)就不需要同步玻募,這種避免共享數(shù)據(jù)的技...
    Mars_M閱讀 1,104評論 0 0
  • 一只损、簡介 并發(fā)編程中,當(dāng)訪問共享數(shù)據(jù)時(shí)七咧,通常需要使用同步技術(shù)跃惫。但如果數(shù)據(jù)不發(fā)布(逸出)到線程以外,僅僅在單線程中被...
    邱simple閱讀 3,441評論 3 12
  • 簡書江溢Jonny艾栋,轉(zhuǎn)載請注明原創(chuàng)出處爆存,謝謝! 本文內(nèi)容將基于JDK1.7的源碼進(jìn)行討論蝗砾,并且在文章的結(jié)尾先较,筆者將...
    江溢jonny閱讀 449評論 0 1
  • 線程封閉與ThreadLocal 多線程訪問共享可變數(shù)據(jù)時(shí),涉及到線程間數(shù)據(jù)同步問題遥诉。然而拇泣,并不是所有時(shí)候都需要共...
    疊最厚的甲閱讀 382評論 0 1