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

The use of ThreadLocal in Java Concurrent Programming and source code analysis Create ThreadLocal and its operation Cr...
Create ThreadLocal
Perform the corresponding operation of ThreadLocal
ThreadLocal initial value setting
ThreadLocal delay setting

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

12 June 2020, 03:08 | Views: 5712

Add new comment

For adding a comment, please log in
or create account

0 comments