1. The ThreadLocal

1.1 Basic Usage

  • Different threads use the same ThreadLocal to store data without interfering with each other.
 	    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
   		hreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
   		
    	private void threadLocalTest(a) {
        / / main thread
        stringThreadLocal.set("baozi111");
        integerThreadLocal.set(111);
        Log.d(TAG, Thread.currentThread().getName() + ":" + stringThreadLocal.get());
        Log.d(TAG, Thread.currentThread().getName() + ":" + integerThreadLocal.get());

        / / the child thread
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                stringThreadLocal.set("baozi222");
                integerThreadLocal.set(222);
                Log.d(TAG, Thread.currentThread().getName() + ":" + stringThreadLocal.get());
                Log.d(TAG, Thread.currentThread().getName() + ":" + integerThreadLocal.get());
            }
        }).start();
    }
Copy the code

1.2 Basic Knowledge

  • The Handler messaging mechanism is very important in Android. Handlers are everywhere. Each thread can only have one Loop, which uses ThreadLocal.
  • What’s the difference between a ThreadLocal and a Synchonized system? A Synchonized system uses a lock mechanism to make variables or code blocks accessible to only one thread and share data, whereas a ThreadLocal system gives each thread a copy of a variable that doesn’t affect each other. Isolates data sharing by multiple threads.
  • The ThreadLocal class interface is simple, with only three methods called to me on a regular basis: set(), get(), and remove().

1.3 the principle

  • ThreadLocals do not store their own content, which is saved by each Thread. ThreadLocals, also known as ThreadLocalMap, is empty by default and initialized only when used. A ThreadLocalMap is an array of entries whose key is ThreadLocal and value is the saved content. Each thread keeps its own data, so threads are separated.

  • In the above Entry array, different ThreadLocal values are computed in different positions, so they are drawn in order for convenience.

2. The ThreadLocal source code

2.1 Construction method

  • The constructor doesn’t have anything, so chances are it’s going to initialize all sorts of things at set time.
    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal(a) {}Copy the code

2.2 ThreadLocalMap class

2.2.1 Entry

  • Source code can see that the Entry class is a weak reference type.
  • Key stores the ThreadLocal and value stores the contents.
        /** * 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<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

2.2.2 Construction method

  • Create an array of 16 entries and store the location using hashcode and 15. Then save the contents.
    /** * 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 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;

        /** * 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); }.}Copy the code

2.2.3 set (ThreadLocal <? > key, Object value)

  • If the calculated location already has content, it overwrites it. Otherwise, it creates an Entry to store it. Then, it determines whether the value reaches the threshold.
       /**
         * 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(); }Copy the code

2.2.4 setInitialValue ()

  • Initialize an empty Entry, because some ThreadLocal has nothing to store at first, but calls get, which has nothing, but still uses a pit and returns NULL.
    /**
     * 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}.
     *
     * <p>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(a) {
        return null;
    }

    /**
     * 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(a) {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
Copy the code

2.2.5 getEntry (ThreadLocal <? > key)

  • Use hashCode to find the corresponding Entry. If it is best to find the Entry once, but it may not be able to find the Entry once after expansion, the getEntryAfterMiss method should be executed to find the Entry.
        /** * 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;
        }
Copy the code

2.2.6 Capacity Expansion

  • Threshold, which is 2/3 of the array capacity.
  • When the condition if (! **rehash()** cleanSomeSlots(I, sz) && sz >= threshold)
  • If (size >= threshold-threshold / 4) is met, capacity expansion will be triggered, and capacity expansion will be doubled.
        /** * Set the resize threshold to maintain at worst a 2/3 load factor. */
        private void setThreshold(int len) {
            // The length of the array is 2/3
            threshold = len * 2 / 3;
        }
        
        /** * 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(a) {
            / / finishing
            expungeStaleEntries();

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

        /** * Double the capacity of the table. */
        private void resize(a) {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            // Make it twice as large as before
            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;
        }
Copy the code

2.3 the set value (T)

  • This method assigns a value to a copy of the currentThread, which is used directly in the Thread itself, and is automatically switched to the currentThread thread.currentthread ().
  • There is a **ThreadLocalMap **, and we can see that the current thread is stored in it.
  • If the map exists we pass in this which is a ThreadLocal and a value that we want to save, otherwise we pass in the current thread and the 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);
    }
Copy the code

2.3.1 the set value) (this,

  • The threadLocalmap. set method is called directly to store or replace the content.

2.3.2 createMap(Thread t, T firstValue)

  • To create a Map, call the ThreadLocalMap constructor.
    /**
     * 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);
    }
Copy the code

2.4 the get ()

  • Obtain the ThreadLocalMap of the current thread, and then search for the corresponding Entry in ThreadLocalMap. If no Entry is found, create an Entry and occupy a pit.
    /**
     * 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(a) {
    	// Get the current thread
        Thread t = Thread.currentThread();
        // Get the ThreadLocalMap of the current thread
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}return setInitialValue();
    }
Copy the code

3. The last

  • If there is a mistake, I hope you help to point out, thank you ~~