From the school to A factory all the way sunshine vicissitudes of life

Please go to www.codercc.com


1. Introduction to ThreadLocal

In multithreaded programming, thread safety is usually addressed by synchronzed or lock control of the order in which threads synchronize critical resources. However, this locking method is not very efficient because threads that do not acquire the lock will block and wait. Thread safety problem is at the heart of multiple threads will be for the same critical region to a Shared resource, so, if each thread to use their own “sharing resource”, their use of their own, and do not affect each other is to reach the state of the isolation between multiple threads so as not to appear thread safety problem. In effect, this is a “space for time” scheme, in which each thread has its own “shared resource”, no doubt much more memory, but the lack of synchronization reduces the number of times threads can block and wait, thus improving time efficiency.

Although ThreadLocal is not in the java.util.concurrent package but in the java.lang package, I prefer to categorize it as a concurrent container (although ThreadLoclMap is the one that actually holds the data). The ThreadLocal class is a “local variable” of a thread. Each thread has a copy of the local variable, so that each thread can use it separately to avoid competing for shared resources.

2. Implementation principle of ThreadLocal

To learn how ThreadLocal works, you need to understand its core methods, including how to store and retrieve ThreadLocal. Let’s take a look at each one.

void set(T value)

The set method sets the value of the threadLocal variable in the current thread.

public void set(T value) { //1. Thread t = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! Set (this, value); set(this, value); Create a ThreadLocalMap and store value createMap(t, value); }Copy the code

The logic of the method is clear, see the comments above. A value is stored in a ThreadLocalMap. A value is stored in a ThreadLocalMap. A value is stored in a ThreadLocalMap. And the current threadLocal instance is the key. Let’s take a quick look at what ThreadLocalMap is and get a sense of what it is.

How does ThreadLocalMap come about in the first place? GetMap (t) getMap(t)

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code

This method returns threadLocals, a member of the current thread object T:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

That is, the reference to ThreadLocalMap is maintained by the Thread as a member variable. If a map is Null, the createMap(t, value) method is used:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

This method is to create a new ThreadLocalMap instance object and store it in the ThreadLocalMap with the current threadLocal instance as the key and value value. It then assigns the threadLocals value of the current thread object to threadLocalMap.

Now to sum up the set method: Obtain the threadLocalMap maintained by the current thread object thread. If threadLocalMap is not null, the key-value pair of threadLocal instance is used as key and value is value ThreadLocalMap, if threadLocalMap is null, create a new threadLocalMap and store it in a key-value pair with threadLocal as the key and value as value.

T get()

The get method gets the value of the threadLocal variable in the current thread.

Thread T = thread.currentThread (); public T get() {//1. ThreadLocalMap map = getMap(t); if (map ! Threadlocalmap. entry e = map.getentry (this); // getEntry threadlocalmap. entry e = map.getentry (this); if (e ! = null) { @SuppressWarnings("unchecked") //4. Value T result = (T)e.value; return result; }} //5. If map or entry is null, use this method to initialize and return the value returned by this method. Return setInitialValue(); }Copy the code

Understand the logic of the set method, look at the get method just need to look at it with reverse thinking, if it is saved like that, turn around to take it. Look at the comments for the code logic. Also, what does setInitialValue basically do?

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; }Copy the code

The logic of this method is almost the same as that of the set method. Another thing worth noting is the initialValue method:

protected T initialValue() {
    return null;
}
Copy the code

This method is protected, which means that subclasses of ThreadLocal can override it to assign to other initial values. To sum up about the GET method:

Obtain the threadLocalMap maintained by the current thread instance, and then obtain the Entry pairs in the map using the current threadLocal instance as the key. If the Entry is not null, the value of the Entry is returned. If threadLocalMap or Entry is null, the current threadLocal is used as the Key and value is null, and the map is stored and null is returned.

void remove()

ThreadLocalMap m = getMap(thread.currentThread ()); threadLocalMap m = getMap(thread.currentThread ()); if (m ! = null) //2. Delete the key pair m.remove(this) from the map with the current threadLocal instance as the key; }Copy the code

The get and set methods save and read data, but we also have to learn how to delete data. To delete data from a map, obtain the threadLocalMap associated with the current thread and then delete the key-value pair of the threadLocal instance as key from the map.

3. ThreadLocalMap explanation

From the above analysis, we know that the data is actually stored in the threadLocalMap, threadLocal get, The set and remove methods are actually implemented specifically through the getEntry,set and remove methods of threadLocalMap. To truly understand threadLocal, you need to understand threadLocalMap.

3.1 Entry Data structure

ThreadLocalMap is a static inner class of threadLocal. Like most containers, ThreadLocalMap maintains an array of tables of type Entry.

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;
Copy the code

As you can see from the comments, the length of the table array is a power of two. Let’s see what Entry is:

static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value;
Entry(ThreadLocal&lt; ? &gt; k, Object v) { super(k); value = v; }Copy the code

Copy the code

}

Entry is a key-value pair with ThreadLocal as key and Object as value. In addition, it should be noted that ** ThreadLocal is a WeakReference, because Entry inherits WeakReference. In the Entry constructor, calling the super(k) method wraps the threadLocal instance as a WeakReferenece. * * here we can use a diagram (below from http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/) to understand the thread, threadLocal, threadLocalMap, Relationships between entries:

Relationships between references of ThreadLocal

Note that solid lines in the figure above represent strong references and dotted lines represent weak references. As shown in the figure, a threadLocalMap can be obtained by threadLocals from each thread instance, which is actually an array of entries with the threadLocal instance as the key and any object as the value. When we assign a value to a threadLocal variable, we actually store an Entry of value into the threadLocalMap with the current threadLocal instance as the key. If a strong reference is set to NULL (threadLocalInstance= NULL), then the system will perform reachabability analysis during GC. The threadLocal instance will not have any links that can reference it, and the threadLocal will be reclaimed, resulting in null entries in the ThreadLocalMap. There is no way to access the value of the Entry with a null key. If the current thread does not terminate, the value of the Entry with a null key will remain a strong reference chain: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value can never be reclaimed, causing a memory leak. ** If threadLocal, threadLocalMap, and Entry are not reachable, they will all be collected by the system during garbage collection. In practice, thread pools are used to maintain the creation and reuse of threads, such as fixed size thread pools. Threads will not voluntarily terminate for reuse. Therefore, the memory leakage problem of threadLocal should be considered and paid attention to. See this article —- for details on threadLocal memory leaks

3.2 set method

ThreadLocalMap, like concurrentHashMap, hashMap, etc., is implemented using hash tables. Before we look at the set method, let’s review what we know about hash tables (from the threadLocalMap tutorial of this article and the hash of this article).

  • Hash table

Ideally, a hash table is a fixed-size array containing keywords that are mapped to different locations in the array using hash functions. The following is

A diagram of an ideal hash table

Ideally, the hash function can spread the keywords evenly across different parts of the array, without the two keys having the same hash value (assuming the number of keys is smaller than the size of the array). In practice, however, it is common for multiple keywords to have the same hash value (mapped to the same location in the array), which we call hash collisions. To solve hashing conflicts, the following two methods are used: Separate chaining and Open Addressing

  • Separated linked list method

The decentralized linked list method uses a linked list to resolve conflicts by storing all elements with the same hash value in a single linked list. When querying, it first finds the linked list of the element, and then traverses the list to find the corresponding element, which is typically implemented as the zipper method of hashMap and concurrentHashMap. Here’s a schematic:

Diagram of separated linked list method

Images from http://faculty.cs.niu.edu/~freedman/340/340notes/340hash.htm

  • Open addressing

Open addressing does not create a linked list. When a key is hashed to an array cell that is already occupied by another key, it tries to find another cell in the array until an empty cell is found. There are many ways to detect the empty element of the array. Here is a simple linear detection method. Linear detection is to start from the conflicting array elements, search for empty cells, and if you get to the end of the array, search from the beginning (ring search). As shown below:

Schematic diagram of open addressing

Images from http://alexyyek.github.io/2014/12/14/hashCollapse/

For a comparison of the two approaches, see this article. ThreadLocalMap uses the open address method to handle hash collisions, while HashMap uses the separated linked list method. The main reason for the difference is that the hash values in ThreadLocalMap are evenly distributed and rarely collide. And ThreadLocalMap often needs to clean up useless objects, making it easier to use pure arrays.

With that in mind, let’s go back to the set method. 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 &amp; int I = key.threadLocalHashCode &amp; (len-1); For (Entry e = TAB [I]; for (Entry e = TAB [I]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal&lt; ? &gt; k = e.get(); // Overwrite old Entry if (k == key) {e.value = value; return; } // If the key is null, the strong reference to threadLocal has been released, and the corresponding entry of threadLocalMap cannot be obtained by using this key. If (k == null) {replaceStaleEntry(key, value, I); replaceStaleEntry(key, value, I); return; TAB [I] = new entry (key, value); int sz = ++size; // Remove some dirty entries whose key is NULL. If the number of entries exceeds the threshold, expand the number. If (! cleanSomeSlots(i, sz) &amp; &amp; sz &gt; = threshold) rehash();Copy the code

Copy the code

}

For the key parts of the set method, see the comments above.

  1. ThreadLocal hashcode?

    private final int threadLocalHashCode = nextHashCode();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static AtomicInteger nextHashCode =new AtomicInteger();
    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    Copy the code

    It is clear from the source that the hashCode of threadLocal instances is implemented through the nextHashCode() method, which is actually always implemented with an AtomicInteger plus 0x61C88647. The 0x61C88647 number is unique in that it ensures that each hash bucket of a hash table is evenly distributed. This is Fibonacci Hashing. For more on this article, see the threadLocal hash values section. ThreadLocal uses the open address method to resolve hash collisions.

  2. How do I determine where to insert a new value into the hash table?

    The source code for this operation is: Key.threadlocalhashcode& (len-1), in the same way as containers such as hashMap and ConcurrentHashMap, uses the hashcode of the current key(threadLocal instance) to match the size of the hash table. Since the hash table size is always a power of two, the sum is equivalent to a modular process that can be allocated to a specific hash bucket by Key. And the reason why modulo takes place through bitwise and operation is that bitwise operation is much more efficient than modulo operation.

  3. How to resolve hash conflicts?

    NextIndex (I, len) : ((I + 1 < len)? i + 1 : 0); That is, it goes back and forth linearly, and when you get to the end of the hash table, you start at zero, and you circle.

  4. How to solve “dirty” Entry?

    On the analysis of the threadLocal, threadLocalMap and the relationship between Entry, we already know that using threadLocal there may be a memory leak (after the object is created, in after logic has been without the use of the object, However, the garbage collector cannot reclaim the memory of this part. In the source code, such an Entry with a null key is called “stale Entry”, which literally means “stale Entry”. I interpret it as “dirty Entry”. Josh Bloch and Doug Lea took this situation into account and used the replaceStaleEntry method to solve the dirty entry problem while looking for the same overwritable entry as the current Key in the for loop of the set method. If the current table[I] is null, inserting a new entry directly will also solve the problem of dirty entry through cleanSomeSlots. ThreadLocal memory leaks will be covered in detail in that article

  5. How do I expand capacity?

To determine the threshold of

Like most containers, threadLocalMap has a scaling mechanism, so how does its threshold work? private int threshold; // Default to 0 /** _ The initial capacity — MUST be a power of two. _/ private static final int INITIAL_CAPACITY = 16; 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); } /** _ Set the resize threshold to maintain at worst a 2/3 load factor. _/ private void setThreshold(int len) { threshold = len * 2 / 3; }

ThreadLocalMap with an initial size of 16 will be created when threadLocal is assigned for the first time, and threshold will be set by setThreshold, whose value is the current hash array length multiplied by (2/3). In other words, the load factor is 2/3(The load factor is a parameter measuring the density of hash tables. If the load factor is larger, the more hash tables are loaded, the more likely hash conflicts will occur; otherwise, the less loaded, the less likely hash conflicts will occur. If too small at the same time, it is clear that the memory utilization rate is not high, this value should be considering the memory usage and the hash values conflict probability of a balance, such as a hashMap, concurrentHashMap load factor of 0.75). Here, the initial size of threadLocalMap is 16 and the loading factor is 2/3, so the available size of the hash table is: 16*2/3=10, that is, the available capacity of the hash table is 10.

Expanding the resize

According to the set method, when the size of the hash table is greater than threshold, the hash table will be expanded by 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 &lt; oldLen; ++j) { Entry e = oldTab[j]; if (e ! = null) { ThreadLocal&lt; ? &gt; k = e.get(); If (k == null) {e.value = null; if (k == null) {e.value = null; // Help the GC} else {// Reposition the entry in the new array, then insert int h = k.htReadLocalHashCode &amp; (newLen - 1); while (newTab[h] ! = null) h = nextIndex(h, newLen); newTab[h] = e; count++; // Set threshHold and size properties setThreshold(newLen); size = count; table = newTab;Copy the code

Copy the code

}

Create an array whose size is twice the length of the original array. Then iterate over the entries in the old array and insert them into the new hash array. Note that in the expansion process, the value of dirty entries will be set to null so that they can be collected by the garbage collector. Resolve hidden memory leaks.

3.3 the getEntry method

GetEntry = getEntry;

private Entry getEntry(ThreadLocal<? Int I = key.threadLocalHashCode & (table.length-1); Entry e = table[I]; //3. Return the entry if (e! = null && e.get() == key) return e; Return getEntryAfterMiss(key, I, e); return getEntryAfterMiss(key, I, e); }Copy the code

The logic of the method is very simple. If the key of the currently located entry is the same as that of the search key, the entry will be returned directly. Otherwise, there will be a hash conflict during the set process, which needs to be further handled by getEntryAfterMiss. The getEntryAfterMiss method is:

private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length;
while (e ! = null) { ThreadLocal&lt; ? &gt; k = e.get(); If (k == key) // Return e if (k == key) If (k == null) // Resolve dirty entry problem expungeStaleEntry(I); Else I = nextIndex(I, len); e = tab[i]; } return null;Copy the code

Copy the code

}

This method is also easy to understand. The nextIndex is used to search through the loop. If an entry with the same key as the queried key is found, it will be returned directly. So far, in order to address potential memory leaks, dirty entries have been processed in places like SET, resize, and getEntry. Efforts have been made almost all the time to solve this problem. **

3.4 remove

/** * 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 (LLDB () == key) {// set entry key to null e.clear(); // Set the value of this entry to null expungeStaleEntry(I); return; }}}Copy the code

The logic of this method is very simple. After finding an entry that is the same as the specified key through the subsequent ring, the key is set to NULL through the clear method to convert it into a dirty entry. The expungeStaleEntry method is then called to set its value to NULL for garbage collection, and the table[I] is set to NULL.

4. Usage scenarios of ThreadLocal

ThreadLocal is not designed to solve the problem of multi-threaded access to shared objects. The data is essentially placed in the threadLocalMap referenced by each thread instance. That is, each different thread has its own data container (threadLocalMap), which does not affect each other. Thus threadLocal is only suitable for business scenarios where sharing objects would be thread-safe. For example, In Hibernate, Session management through threadLocal is a typical case. Different requesting threads (users) have their own sessions. If a Session is shared and accessed by multiple threads, it will inevitably bring thread safety problems. The simpleDateFormat. parse method has thread safety issues. We can try wrapping SimpleDateFormat with threadLocal so that the instance is not shared by multiple threads.

public class ThreadLocalDemo {
    private static ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<>();
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i &lt; 100; i++) { executorService.submit(new DateUtil("2019-11-25 09:00:" + i % 60)); } } static class DateUtil implements Runnable { private String date; public DateUtil(String date) { this.date = date; } @Override public void run() { if (sdf.get() == null) { sdf.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } else { try { Date date = sdf.get().parse(this.date); System.out.println(date); } catch (ParseException e) { e.printStackTrace(); }}}}Copy the code

Copy the code

}

  1. If the current thread doesn’t hold an instance of the SimpleDateformat object, create a new one and set it to the current thread, or use it if it already does. Also, from if (sdf.get() == null){…. }else{….. } You can see that the allocation of a SimpleDateformat object instance for each thread is guaranteed at the application level (business code logic).
  2. ThreadLocal can leak memory, so it is best to remove the variable using the remove method, just as you would with a database connection, to close the connection.

The resources

  1. Java High Concurrency Programming
  2. This article has a good threadLocalMap and hashCode for threadLocal
  3. This article explains hash, which is good
  4. Resolve hash conflict chain address method and open address method comparison