The use of ThreadLocal in Java Concurrent Programming and source code analysis

The use of ThreadLocal in Java Concurrent Programming and source code analysis


The Java ThreadLocal class allows you to create variables that can only be read and written by the same thread. Therefore, even if two threads execute the same code and the code references the same ThreadLocal variable, two threads cannot see each other's ThreadLocal variable.

Create ThreadLocal and its operation

Create ThreadLocal

ThreadLocal<String> threadLocal = new ThreadLocal<>();

The above code is to create a ThreadLocal object

Perform the corresponding operation of ThreadLocal

Set the value of ThreadLocal, and use the set method (get the current thread and search in the map):

threadlocal.set("Hello World");

Get the value of ThreadLocal and use the get method (get the current thread and search in the map):

threadlocal.get();

Delete the value of ThreadLocal, and use the remove method (get the current thread and search in the map):

threadlocal.remove();

ThreadLocal initial value setting

ThreadLocal can set the initialization value in two ways:
1. Override initialValue() with subclass to set

ThreadLocal<String> threadLocalInit = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return String.valueOf(System.currentTimeMillis());
        }
    };

2. Use ThreadLocal to implement the Supplier interface for setting

ThreadLocal<String> threadLocalInit2 = ThreadLocal.withInitial(new Supplier<String>() {
        @Override
        public String get() {
            return String.valueOf(System.currentTimeMillis());
        }
    });

Set using Lambda expressions

ThreadLocal<String> threadLocalInit3 = ThreadLocal.withInitial(
        ()->{return String.valueOf(System.currentTimeMillis());});

	//Simplified version
    ThreadLocal<String> threadLocalInit4 = ThreadLocal.withInitial(() -> String.valueOf(System.currentTimeMillis()));

ThreadLocal delay setting

In some cases, you cannot use the standard method of setting the initial value. For example, you may need some configuration information that is not available when you create a ThreadLocal variable. In this case, you can delay setting the initial value.

public class DateFormatter {
    private ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();

    public String format(Date date){
        SimpleDateFormat simpleDateFormat = getSimpleDateFormat();
        return simpleDateFormat.format(date);
    }

    private SimpleDateFormat getSimpleDateFormat() {
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        if(Objects.isNull(simpleDateFormat)){
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            threadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }
}
//If hibernate is used, it should have an impact. When we create sessionFactory, we will use the above method.
//Use the sessionFactory in ThreadLocal for delayed creation.

ThreadLocal subclass InheritableThreadLocal

The InheritableThreadLocal class is a subclass of ThreadLocal. Instead of each thread inside Threadable having its own value, InheritableThreadLocal grants access to the value of the thread and all the sub threads created by the thread.

ThreadLocal source code

public class ThreadLocal<T> {
	
	private final int threadLocalHashCode = nextHashCode();
	//Thread safe Integer class AtomicInteger
	private static AtomicInteger nextHashCode = new AtomicInteger();
	//The difference between consecutive generated hash codes -- implicitly ordering the thread local id
	//Convert to a hash value close to the optimal diffusion multiplication for tables of two power sizes.
	private static final int HASH_INCREMENT = 0x61c88647;
	//Generate nextHashCode
	private static int nextHashCode() {
    	return nextHashCode.getAndAdd(HASH_INCREMENT);
	}
	//Initialization settings. If the subclass does not override this method, the initialization value cannot be set
	protected T initialValue() {
        return null;
    }
    //Implement the initialization value setting of the interface
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    //Default constructor 
    public ThreadLocal() {
    }
    //Get the value in ThreadLocal
    public T get() {
        Thread t = Thread.currentThread();
        //This is an internal class of ThreadLocal to manage ThreadLocal data
        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();
    }
	//Set initial value
	private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	//Set to create a createMap if it does not exist
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    //Set value
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    //Remove value
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
	//Get internal class ThreadLocalMap 
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    //Create a ThreadLocalMap, where a thread and related values are passed in
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //Create a map in InheritableThreadLocal
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
    //Get child value
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }
    //Implement Supplier to set initialization value
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
        private final Supplier<? extends T> supplier;
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
        //Initialization settings through supplier
        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
    //Inner class ThreadLocalMap 
    static class ThreadLocalMap {
    	//The entry in this hash map extends WeakReference, using its primary ref field as the
    	//Key (always a ThreadLocal object). Note that the empty key (i.e entry.get () = = null) means
    	//The key is no longer referenced, so entries can be removed from the table. Such an entry is shown in the following code
    	//Is referred to as a "stale entry.".
    	static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //Initialize capacity
        private static final int INITIAL_CAPACITY = 16;
        //Reset required table
         private Entry[] table;
         //Entries in map
         private int size = 0;
         //Next size value to resize
         private int threshold
         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);
        }
        //Construct a new mapping that initially contains (firstKey, firstValue).
        //ThreadLocalMaps are constructed lazily, so we only create one when there is at least one entry to put in it
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY]; //Set 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 thread local variables from the given parent map. Only called by createInheritedMap.
        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++;
                    }
                }
            }
        }
        //Gets the entry associated with the key. The method itself only deals with fast paths: direct hits to existing
        //Key. Otherwise, it gets missed for getEntryAfterMiss. The goal is to maximize direct hit performance
        //In part, this is done by making this method easily indivisible.
        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);
        }

        /**
         * The version of the getEntry method used when the key cannot be found in its direct hash slot
         */
        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 the key
         */
        private void set(ThreadLocal<?> key, Object value) {
           //We don't use a fast path like get () because it's located at least as much as creating with set ()
           //New entries are just as common to replace existing ones, in which case paths are more likely to fail than paths.
            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 key from entry
         */
        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;
                }
            }
        }

        /**
         Replaces stale entries encountered during a set operation with entries that specify key values. Incoming value 
         value Parameters are stored in the entry, regardless of whether the entry for the specified key already exists
        As a side effect, this method removes all stale entries in "run" that contain the stale entry. (a run is a sequence of entries between two empty slots. )
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            //Backup to check for obsolete entries in the current run.
			//We clean up the whole operation at one time to avoid continuous operation
			//Incremental rehash due to garbage collector release
			//A large increase in references (that is, 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 running key or trailing space
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

              //If a key is found, the key needs to be exchanged
			//Use stale entries to maintain the order of the hash table.
			//New failed slots, or any other failed slots
			//It can be sent to expungeStaleEntry
			//Delete or rehash all other entries in the 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 no obsolete entries are found in the backward scan, the
				//The first stale entry you see when you scan a key is
				//It's still running first.
                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);
        }

        /**
        Clear stale entries by rehashing any potentially conflicting entries
        Between the staleSlot and the next empty slot. It's gone
        Any other obsolete entries encountered before null at the end
         */
        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 scans some cells for obsolete items.
        Called when a new element is added or another obsolete element is removed. It performs a logarithmic number of scans to
        Balance non scanning (fast but retaining garbage) and the number of scans proportional to the number of elements, so
        All garbage will be found, but some inserts will take O(n) time
         */
        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;
        }

    /**
    Repack and / or resize the table. First scan the whole
	Delete the table of stale entries. If that's not enough
	Reduce the size of the table and double the size of the table
     */
    private void rehash() {
        expungeStaleEntries();
        // Use lower threshold for doubling to avoid hysteresis
        if (size >= threshold - threshold / 4)
            resize();
    }

    /**
     * Reset size to twice previous size
     */
    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; // GC recovery
                } 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;
    }

    /**
     * Delete all obsolete 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);
        }
    }
    }
}

Tags: Java Programming Lambda Hibernate

Posted on Fri, 12 Jun 2020 03:08:19 -0400 by SueHubert