Why ThreadLocal

Officially, this is a way to implement thread closure, such as multi-threaded access to shared variables, a member variable in a class, which is safe in a single-threaded environment and used by only one thread. However, in the case of multiple threads it can be used by multiple threads, and ThreadLocal is designed to ensure that a thread only uses its own member variables.

Simple to use

In ThreadLocal, one of the most common scenarios is database connection retrieval. Consider that if we maintain a shared global connection, then concurrency will cause the previous thread to run out and close the connection. But the current situation is still being queried.


    private static final String DB_URL  = "localhost:3306";

    private static  ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
        /**
         * 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
         */
        @Override
        protected Connection initialValue(a) {
            returnDriverManager.getConnection(DB_URL); }};private static Connection getConnection(a){
        return connectionHolder.get();
    }


Copy the code

The source code parsing

I always feel that the most important thing to see source code is to understand the comments, so I will not directly delete the comments here, will be attached to the comments together to analyze.

Class notes

/**
 * 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.  {@codeThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * The thread's own, * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@codeThredid. Get ()} * and remains unchanged on subsequent calls. * <pre> * Includes methods to generate ids. * 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&lt; Integer&gt; threadId = * new ThreadLocal&lt; Integer&gt; () {* & # 64; 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(); * } * } * </pre> * <p>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
 * @since1.2 * /

Copy the code

If we open ThreadLocal’s source code, we have this comment in its class. What ThreadLocal does and how ThreadId is created?

ThreadLocal structure

The points to note are:

  1. The ThreadLocal instance itself does not store values, it simply provides a key worth finding a copy in the current thread.
  2. A ThreadLocal is contained within a Thread, not the other way around
  3. Each thread has its own ThreadLocalMap.
  4. When a thread puts a value into a ThreadLocal, it stores the value into its own ThreadLocalMap, and reads the value using ThreadLocal as a reference to find the corresponding key in its own map, thus achieving thread isolation

The Entry structure

Entry is a K-V structure where the key is a ThreadLocal and is a weak reference. This WeakReference is what causes ThreadLocal memory to leak.

        /** * 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. * * entry.get() == null is dirty */
        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

ThreadLocal.set()

Let’s first look at the common methods set(value) and get()


    /**
     * 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.
     *
     * @paramValue the value to be stored in the current thread's copy of * this thread-local. * Sets the current thread copy of this thread-local variable to the specified value. Most subclasses will not need to override this method and will rely only on the initialValue method to set the value of the thread initialValue. * /
    public void set(T value) {
        // Get the current thread
        Thread t = Thread.currentThread();
        // Get the ThreadLocalMap of the current thread
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
        // If the map is not empty, place it in a ThreadLocalMap
            map.set(this, value);
        else
        // Otherwise create a ThreadLocalMap
            createMap(t, value);
    }


Copy the code

The implementation of ThreadLocal is very simple, and most of the operations are performed by ThreadLocalMap, so the operations of ThreadLocal are performed by ThreadLocalMap.

getMap()

The code implementation here is a single line, which takes out the threadLocals of the current thread


    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
    // Take out the current thread's threadLocals
        return t.threadLocals;
    }

Copy the code

ThreadLocalMap.set()

The code here is basically setting value. It involves the structure of k-V storage, algorithms for resolving conflicts, and the handling of reclaimed objects.

        /** * 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.
            // It uses Entry to implement k-V storage. Entry implementation will be discussed later
            Entry[] tab = table;
            // Get the length of TAB
            int len = tab.length;
            // Use threadLocalHashCode to calculate the location of the storage
            int i = key.threadLocalHashCode & (len-1);
            // Here is an open addressing method to resolve hash collisions
            for(Entry e = tab[i]; e ! =null;
                 e = tab[i = nextIndex(i, len)]) {
                 / / get the TAB [I]ThreadLocal<? > k = e.get();// Overwrite ThreadLocal if there are already ThreadLocal and they are equal
                if (k == key) {
                    e.value = value;
                    return;
                }
                // If the current position is null(ThreadLocal was reclaimed), replace it
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return; }}// New Entry (key,value) if there are no ThreadLocal elements in the same position and no ThreadLocal has been collected
            tab[i] = new Entry(key, value);
            / / to increase the size
            int sz = ++size;
            // Clear the recycled elements and rehash if sz is greater than or equal to threshold
            if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

ThreadLocalHashCode

ThreadLocalHashCode is essentially a custom hash code. The underlying implementation is AtomicInteger, which uses unsafe for atomic operations


    /** * 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-poured in * less common cases. * Searches for ThreadLocal objects */ using threadLocalHashCode
    private final int threadLocalHashCode = nextHashCode();

    ** * The next hash code to be given out. Updated atomically. His operation is atomic and 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. * Convert the linear ID to a multiplicative hash of an approximately optimal distribution for a power table of 2. * /
    private static final int HASH_INCREMENT = 0x61c88647;

    /** * Returns the next hash code
    private static int nextHashCode(a) {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

Copy the code

replaceStaleEntry

This method is a bit complicated, and it is important to note that the whole process of this method is to try to erase some staleSlot found. The key is empty and should be collected by GC, so why go through this process? Because the key has been recycled, the value has not been recycled, and the entry has not been recycled, so they need to be recycled. I’m not going to do that. I’ll do it later.


        /**
         * 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).
            (LLDB () == null)
            // This operation is used to release the previous dirty entries in one go
            int slotToExpunge = staleSlot;
            for (inti = 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
            // Iterate backwards to find the first dirty key in reverse order
            for (inti = 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 the key already exists
                if (k == key) {
                    //覆盖value
                    e.value = value;
                    // Swap with the previous dirty key
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    SlotToExpunge == staleSlot if slotToExpunge== staleSlot
                    if (slotToExpunge == staleSlot)
                    // Set slotToExpunge = current position
                        slotToExpunge = i;
                        // Clean up
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }


            // If no dirty key is found in the forward search
            // Then we need to set slotToExpunge to the current position

                if (k == null && slotToExpunge == staleSlot)
                // Set slotToExpunge = current position
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            
            // If the key does not exist in the array, create a new one and put it in
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
            
            // If there are other objects that have expired, they need to be cleaned up
            // If there are any other stale entries in run, expunge them
            if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code

As you can see from the source code, the whole process can be divided into two parts. Look forward and look backward. If a dirty key appears in one location, it is likely that dirty keys will also appear in adjacent locations. Therefore, to improve efficiency, this method will process adjacent dirty keys together. So let’s look at some of these cases.

(e = TAB [I]); (e = TAB [I]); = null && e.get() == null

  1. Find dirty keys forward and overwrite keys backward (k == key)

2. The dirty key is found forward, but the overwrite key is not found backward. = key)

3. The dirty key is not found forward, and the overwrite key is found backward (k == key).The logic here is a little more complicated, just remember two things: 1. If you look back and find the dirty key, cleanSomeSlots is the location of the dirty key. 2. If a backward lookup does not find a dirty key, cleanSomeSlots is the current location I.

  1. No dirty key found forward, no overwrite key found backward (k! = key)

The purpose of swapping overridden keys is to prevent two identical keys from appearing

expungeStaleEntry


        /** * 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 * Clears dirty keys *@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
            // Clear the current dirty key
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            // Reduce TAB size
            size--;

            // Rehash until we encounter null
            // Rehash until null is encountered
            Entry e;
            int i;
            for(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// Dirty key cleanup is encountered
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                //rehash
                    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;
        }
Copy the code

So, after this method is executed, there is no dirty key between staleSlot and I.

cleanSomeSlots

There are only two input parameters to note:

  1. I is the return value of expungeStaleEntry. I must not be a dirty key, so start at the next position of I.
  2. N is a scan control parameter, and bit operation is performed on n to control the cycle.

        /** * 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 This approach may result in an O(n) * * time complexity for inserts@paramI a position known NOT to hold a stale entry. The * scan starts at The element after I@param n scan control: {@codeLog2 (n)} cells are scanned, * unless a stale entry is found, in which case * Scanned control * {@codelog2(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.@returnTrue if any stale entries have been removed. * Returns true */
        private boolean cleanSomeSlots(int i, int n) {
            // Result flag
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                // Check whether the key is dirty
                if(e ! =null && e.get() == null) {
                    n = len;
                    removed = true
                    // Call the method that handles dirty keysi = expungeStaleEntry(i); }}while ( (n >>>= 1) != 0); // A shift to the right is the same thing as n over 2
            return removed;
        }
Copy the code

ThreadLocal.get()

Next we look at the threadlocal.get () method. Get is much easier to understand than set. We just have to focus on the setInitialValue method

    /**
     * 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.
     *
     * @returnThe current thread's value of this thread-local * returns the value in the current thread, and calls setInitialValue */ if it does not exist
    public T get(a) {
        // Fetch the current thread
        Thread t = Thread.currentThread();
        // Get the map of the current thread
        ThreadLocalMap map = getMap(t);
        / / the map isn't empty
        if(map ! =null) {
            // Get the Entry of the current thread
            ThreadLocalMap.Entry e = map.getEntry(this);
            / / Entry isn't empty
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}// Call the initialization method
        return setInitialValue();
    }
Copy the code

setInitialValue

This method is also very simple, and you can see that this is similar to the set() method we talked about earlier. Notice the initialValue() method here

    /**
     * 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

initialValue

As you can see, the comment for this method is quite long, mainly stating several points:

  1. This method is called the first time get() is called without calling set().
  2. Normally, this method is called at most once per thread, but it can be called again with subsequent calls to remove and get.
  3. If the initial value of the requirement is non-empty, you must subclass ThreadLocal and override this method.

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

ThreadLocal.remove()

Let’s just focus on the remove() method and the content of this comment.

    /**
     * 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
     * {@codeInitialValue} method in the current thread. * * Calling GET () immediately after deleting the current thread element will cause initialValue to call * * multiple times@since1.5 * /
     public void remove(a) {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if(m ! =null)
             m.remove(this);
     }
Copy the code

remove

Note that expungeStaleEntry is called to clean dirty keys after deletion

        /** * Remove the entry for key. */
        private void remove(ThreadLocal
        key) {
            Entry[] tab = table;
            int len = tab.length;
            // Get the hash of the current thread
            int i = key.threadLocalHashCode & (len-1);
            for(Entry e = tab[i]; e ! =null;
                 e = tab[i = nextIndex(i, len)]) {
                 // Find the destination Entry
                if (e.get() == key) {
                    / / delete the Entry
                    e.clear();
                    // Delete dirty keys
                    expungeStaleEntry(i);
                    return; }}}Copy the code

rehash

We all know that HashMap and ArrayList have their own scaling mechanisms, and ThreadLocalMap actually has its own scaling mechanisms, which are implemented in the Rehash method. In the set() code we know that we rehash when (sz >= threshold).

        /** * 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. * Run expungeStaleEntries to scan the entire table for dirty keys and empty them before deciding whether to expand the table */
        private void rehash(a) {
            // Scan the table to remove all dirty keys
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            // Use a lower threshold to determine whether to expand the capacity, such as size >= 3/4 threshold
            if (size >= threshold - threshold / 4)
               // Capacity expansion method
                resize();
        }
Copy the code

expungeStaleEntries

This is the cleanup of the entire table

        /** * Expunge all stale entries in the table. */
        private void expungeStaleEntries(a) {
            Entry[] tab = table;
            int len = tab.length;
            / / traverse table
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                // Check whether the key is dirty
                if(e ! =null && e.get() == null)
                    // Call the cleanup processexpungeStaleEntry(j); }}Copy the code

resize

It’s just about doubling the capacity.

        /** * Double the capacity of the table
        private void resize(a) {
               
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            // Create a new Entry[] that is twice the size of the original
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            // Iterate through old Entry[]
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if(e ! =null) { ThreadLocal<? > k = e.get();// If key is empty
                    if (k == null) {
                        // Set value to null to prevent memory leaks
                        e.value = null; // Help the GC
                    } else {
                        // Calculate the position
                        int h = k.threadLocalHashCode & (newLen - 1);
                        // Open addressing if the location is occupied
                        while(newTab[h] ! =null)
                            h = nextIndex(h, newLen);
                        // Set the locationnewTab[h] = e; count++; }}}// Set the threshold
            setThreshold(newLen);
            size = count;
            table = newTab;
        }


Copy the code

Open addressing

The so-called open addressing method is to look for the next empty hash address as soon as a conflict occurs. As long as the hash table is large enough, the empty hash address will always be found and the record will be saved. Let’s briefly talk about linear probing in open addressing, which we use in ThreadLocal

Linear detection method

Linear detection, as the name suggests, is a function that resolves conflicts linearly. TreadLocal code uses such a collision resolution function.

 f(x)= x+1
Copy the code

Note that TreadLocal is a circular probe that, if it reaches the boundary, crosses it to the other end.

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

Copy the code

Advantages of linear detection method:

  1. No extra space (compared to the zipper method, extra linked lists are required)
  2. Detection sequences are local and can take advantage of system caches to reduce IO (contiguous memory addresses)

Disadvantages:

  1. Elapsed time >O(1) (worst O(n))
  2. Increasing conflicts – Past conflicts lead to successive serial conflicts (time complexity approaches O(n))

Why weak references

We mentioned above that entries in ThreadLocal use weak references. Let’s see why we use weak references.

If entry. key points to a ThreadLocal, the reference is dashed and weak. If ThreadLocal is null, the reference will be collected correctly because there is no strong reference.

For example, this code:

public class test {
    private void threadLocalTest(a){
        ThreadLocal<String> threadLocal = newThreadLocal<>(); System.out.println(threadLocal.get()); }}Copy the code

When my threadLocalTest method ends, the threadLocal object will be reclaimed, but if threadLocal and Entry.key are not weak references but strong references, then the threadLocal object will not be reclaimed by the GC, causing a memory leak. It is only reclaimed when the thread dies.

Of course, the practice of using weak references does not completely prevent memory leaks, since value still suffers from memory leaks. So when we set(),get() we clean up the dirty keys.

Recommended usage of ThreadLocal:

  1. Designed to be static, strongly referenced by the class object, the thread is not recycled for its lifetime
  2. The interiors of long objects that are designed to be non-static, such as objects managed by Spring, are also not recycled
  3. It is best not to create ThreadLocal objects in methods

Finally, weak references can also improve JVM memory efficiency, which is useful if we do JDK caches ourselves instead of importing third-party caches like Redis.

About 0 x61c88647

As you can see from the previous code, the Hash algorithm grows to 0x61C88647, which is a very special value.


    /** * 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. * Convert the linear ID to a multiplicative hash of an approximately optimal distribution for a power table of 2. * /
    private static final int HASH_INCREMENT = 0x61c88647;

Copy the code

One of the problems with linear detection, as we said before, is that once you have a collision, you’re likely to have a collision every time after that, leading to a pileup. Using the value 0x61C88647 to create a hash increment can solve this problem to some extent so that the generated values are evenly distributed in the array of powers of 2. That is, when we add 0x61C88647 as a step to assign each ThreadLocal its own ID, threadLocalHashCode modulo with a power of 2, the results are evenly distributed.

0x61C88647 Selection is actually related to Fibonacci hashes, and that’s the math, so I’m not going to expand it here.

TODO

  1. ThreadLocal of actual combat

Personal blog

The stones of Sisyphus

The author is of limited level, please point out any errors and omissions.

Refer to the article

1. ThreadLocal bombarded by big factory interviewers (knowing every detail of source code and design principles)

The ThreadLocal memory leak problem is solved in detail

3. Why use 0x61C88647

4. Open addressing — Linear Probing

Reference books

  1. Java Concurrent Programming