ThreadLocal源码

/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * *

For example, the class below generates unique identifiers local to each

* thread.

* A thread's id is assigned the first time it invokes {@code ThreadId.get()}

* and remains unchanged on subsequent calls.

*

* import java.util.concurrent.atomic.AtomicInteger;

*

* public class ThreadId {

*    // Atomic integer containing the next thread ID to be assigned

*    private static final AtomicInteger nextId = new AtomicInteger(0);

*

*    // Thread local variable containing each thread's ID

*    private static final ThreadLocal threadId =

*        new ThreadLocal() {

*            @Override protected Integer initialValue() {

*                return nextId.getAndIncrement();

*        }

*    };

*

*    // Returns the current thread's unique ID, assigning it if necessary

*    public static int get() {

*        return threadId.get();

*    }

* }

*

*

Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author  Josh Bloch and Doug Lea * @since  1.2 */public class ThreadLocal {

    /**

    * ThreadLocals rely on per-thread linear-probe hash maps attached

    * to each thread (Thread.threadLocals and

    * inheritableThreadLocals).  The ThreadLocal objects act as keys,

    * searched via threadLocalHashCode.  This is a custom hash code

    * (useful only within ThreadLocalMaps) that eliminates collisions

    * in the common case where consecutively constructed ThreadLocals

    * are used by the same threads, while remaining well-behaved in

    * less common cases.

    */

    private final int threadLocalHashCode = nextHashCode();

    /**

    * The next hash code to be given out. Updated atomically. Starts at

    * zero.

    */

    private static AtomicInteger nextHashCode =

        new AtomicInteger();

    /**

    * The difference between successively generated hash codes - turns

    * implicit sequential thread-local IDs into near-optimally spread

    * multiplicative hash values for power-of-two-sized tables.

    */

    private static final int HASH_INCREMENT = 0x61c88647;

    /**

    * Returns the next hash code.

    */

    private static int nextHashCode() {

        return nextHashCode.getAndAdd(HASH_INCREMENT);

    }

    /**

    * Returns the current thread's "initial value" for this

    * thread-local variable.  This method will be invoked the first

    * time a thread accesses the variable with the {@link #get}

    * method, unless the thread previously invoked the {@link #set}

    * method, in which case the {@code initialValue} method will not

    * be invoked for the thread.  Normally, this method is invoked at

    * most once per thread, but it may be invoked again in case of

    * subsequent invocations of {@link #remove} followed by {@link #get}.

    *

    *

This implementation simply returns {@code null}; if the    * programmer desires thread-local variables to have an initial    * value other than {@code null}, {@code ThreadLocal} must be    * subclassed, and this method overridden.  Typically, an    * anonymous inner class will be used.    *    * @return the initial value for this thread-local    */    protected T initialValue() {        return null;    }    /**    * Creates a thread local variable. The initial value of the variable is    * determined by invoking the {@code get} method on the {@code Supplier}.    *    * @paramthe type of the thread local's value    * @param supplier the supplier to be used to determine the initial value    * @return a new thread local variable    * @throws NullPointerException if the specified supplier is null    * @since 1.8    */    public staticThreadLocalwithInitial(Supplier? extends Ssupplier) {        return new SuppliedThreadLocal<>(supplier);    }    /**    * Creates a thread local variable.    * @see #withInitial(java.util.function.Supplier)    */    public ThreadLocal() {    }    /**    * Returns the value in the current thread's copy of this    * thread-local variable.  If the variable has no value for the    * current thread, it is first initialized to the value returned    * by an invocation of the {@link #initialValue} method.    *    * @return the current thread's value of this thread-local    */    public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }    /**    * Variant of set() to establish initialValue. Used instead    * of set() in case user has overridden the set() method.    *    * @return the initial value    */    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;    }    /**    * Sets the current thread's copy of this thread-local variable    * to the specified value.  Most subclasses will have no need to    * override this method, relying solely on the {@link #initialValue}    * method to set the values of thread-locals.    *    * @param value the value to be stored in the current thread's copy of    *        this thread-local.    */    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }    /**    * Removes the current thread's value for this thread-local    * variable.  If this thread-local variable is subsequently    * {@linkplain #get read} by the current thread, its value will be    * reinitialized by invoking its {@link #initialValue} method,    * unless its value is {@linkplain #set set} by the current thread    * in the interim.  This may result in multiple invocations of the    * {@code initialValue} method in the current thread.    *    * @since 1.5    */    public void remove() {        ThreadLocalMap m = getMap(Thread.currentThread());        if (m != null)            m.remove(this);    }    /**    * Get the map associated with a ThreadLocal. Overridden in    * InheritableThreadLocal.    *    * @param  t the current thread    * @return the map    */    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }    /**    * Create the map associated with a ThreadLocal. Overridden in    * InheritableThreadLocal.    *    * @param t the current thread    * @param firstValue value for the initial entry of the map    */    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }    /**    * Factory method to create map of inherited thread locals.    * Designed to be called only from Thread constructor.    *    * @param  parentMap the map associated with parent thread    * @return a map containing the parent's inheritable bindings    */    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {        return new ThreadLocalMap(parentMap);    }    /**    * Method childValue is visibly defined in subclass    * InheritableThreadLocal, but is internally defined here for the    * sake of providing createInheritedMap factory method without    * needing to subclass the map class in InheritableThreadLocal.    * This technique is preferable to the alternative of embedding    * instanceof tests in methods.    */    T childValue(T parentValue) {        throw new UnsupportedOperationException();    }    /**    * An extension of ThreadLocal that obtains its initial value from    * the specified {@code Supplier}.    */    static final class SuppliedThreadLocalextends ThreadLocal{        private final Supplier? extends Tsupplier;        SuppliedThreadLocal(Supplier? extends Tsupplier) {            this.supplier = Objects.requireNonNull(supplier);        }        @Override        protected T initialValue() {            return supplier.get();        }    }    /**    * ThreadLocalMap is a customized hash map suitable only for    * maintaining thread local values. No operations are exported    * outside of the ThreadLocal class. The class is package private to    * allow declaration of fields in class Thread.  To help deal with    * very large and long-lived usages, the hash table entries use    * WeakReferences for keys. However, since reference queues are not    * used, stale entries are guaranteed to be removed only when    * the table starts running out of space.    */    static class ThreadLocalMap {        /**        * The entries in this hash map extend WeakReference, using        * its main ref field as the key (which is always a        * ThreadLocal object).  Note that null keys (i.e. entry.get()        * == null) mean that the key is no longer referenced, so the        * entry can be expunged from table.  Such entries are referred to        * as "stale entries" in the code that follows.        */        static class Entry extends WeakReference> {            /** 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        /**        * Set the resize threshold to maintain at worst a 2/3 load factor.        */        private void setThreshold(int len) {            threshold = len * 2 / 3;        }        /**        * Increment i modulo len.        */        private static int nextIndex(int i, int len) {            return ((i + 1 < len) ? i + 1 : 0);        }        /**        * Decrement i modulo len.        */        private static int prevIndex(int i, int len) {            return ((i - 1 >= 0) ? i - 1 : len - 1);        }        /**        * Construct a new map initially containing (firstKey, firstValue).        * ThreadLocalMaps are constructed lazily, so we only create        * one when we have at least one entry to put in it.        */        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);        }        /**        * Construct a new map including all Inheritable ThreadLocals        * from given parent map. Called only by createInheritedMap.        *        * @param parentMap the map associated with parent thread.        */        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")                    ThreadLocalkey = (ThreadLocal) 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++;

                    }

                }

            }

        }

        /**

        * Get the entry associated with key.  This method

        * itself handles only the fast path: a direct hit of existing

        * key. It otherwise relays to getEntryAfterMiss.  This is

        * designed to maximize performance for direct hits, in part

        * by making this method readily inlinable.

        *

        * @param  key the thread local object

        * @return the entry associated with key, or null if no such

        */

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

            }

        }

    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容

  • 引言 ThreadLocal,线程变量,线程可以将本次线程内经常使用的变量存储到ThreadLocal中,方便本次...
    miaoLoveCode阅读 1,594评论 6 5
  • ThreadLocal和线程同步机制相比:都是为了解决多线程中相同变量的访问冲突问题。在同步机制中,通过对象的锁机...
    tiancijiaren阅读 378评论 0 1
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,449评论 0 13
  • 1.HashMap是一个数组+链表/红黑树的结构,数组的下标在HashMap中称为Bucket值,每个数组项对应的...
    谁在烽烟彼岸阅读 1,020评论 2 2
  • 上午继续去画画,最后一节课,画的连年有鱼,画的很好,也得到了老师的表扬,结束了寒假班的课程,马上就要开学了。做好开...
    郑彩云_aa1b阅读 170评论 0 0