ThreadLocal parsing of Java concurrent programming

Links to the original text: https://juejin.im/post/5d7771c9f265da03c34c25da


This article discusses ThreadLocal in JDK 1.8

(If you want to learn programming by yourself, please search Circle T community More industry-related information and more industry-related free video tutorials. It's totally free!

ThreadLocal concept

ThreadLocal multi-threaded concurrent access to variables solution, for each thread to provide a copy of variables, space for time.

  • ThreadLocal creates a copy of this variable in each thread, that is, there will be one variable in each thread, and it can be used anywhere in the thread. ThreadLocal does not affect each other and implements thread isolation, so there is no thread security problem and it will not seriously affect the performance of program execution.
  • Since replicas are created in each thread, it is important to consider resource consumption, such as memory usage that is larger than that without ThreadLocal.
  • If you use ThreadLocal, it is usually defined as the private static type, and in my opinion, it is better to define the private static final type.

ThreadLocal usage scenario

Personally, I think we can consider using ThreadLocal as long as we meet the following two requirements

  • Each thread needs its own separate instance
  • Instances need to be shared in multiple methods, but do not want to be shared by multiple threads

For example: create a database connection, in the case of multi-threading, we certainly do not want to have thread A get the connection not finished, thread B will close it or multiple threads share a connection, resulting in data manipulation confusion and so on. And our correct posture should have the following similar codes:

private static ThreadLocal<Connection> connTl = new ThreadLocal<>();

public static Connection getConnection() throws SQLException{
    Connection conn = connTl.get();
    if(conn==null){
        conn = dataSource.getConnection();
        connTl.set(conn);
    }
    return conn;
}

Introduction of ThreadLocal Common Methods

class ThreadLocal<T> {
    T get();
    void set(T value);
    void remove();
}

Set the value of the thread local variable for the current thread

public void set(T value);

Returns the thread local variable corresponding to the current thread

public T get();

Delete the value of the thread's current thread local variable

public void remove()

ThreadLocal source code parsing

Before looking at the source code for common methods, let's look at ThreadLocalMap

ThreadLocalMap is a static class within ThreadLocal

 static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * Initial capacity
         */
        private static final int INITIAL_CAPACITY = 16;

        /** 
        * Entity table 
        */
        private Entry[] table;

        /**
         * Table Initial Size
         */
        private int size = 0;

        /**
         * When size reaches threashold, it needs to resize the whole Map. The initial value of threshold is len * 2/3.
         */
        private int threshold; // Default to 0

        /**
         * Set the adjustment size threshold to maintain 2/3 of the load factor in the worst case.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Gets the next index, returns 0 beyond length
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Returns the last index, if - 1 is negative, the index of length - 1
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct parameters to create a ThreadLocalMap code
         * ThreadLocal For key, our generic is value
         */
        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);
        }

        /**
         * ThreadLocal It's thread-isolated, so it's reasonable that there won't be any behavior of data sharing and transmission.
         * This is Inheritable ThreadLocal, which provides a mechanism for data sharing between father and son.
         */
        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++;
                    }
                }
            }
        }

        /**
         * Get the index location of ThreadLocal and get the content through subscript index
         */
        private Entry getEntry(ThreadLocal<?> key) {
            // Subscription determination by hashcode
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            // If found, return directly
            if (e != null && e.get() == key)
                return e;
            else
                // If you can't find it, then you start traversing backwards from position i. Based on the linear detection method, it is possible to find it after position i.
                return getEntryAfterMiss(key, i, e);
        }
        
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            // Cyclic backward traversal
            while (e != null) {
                // Get the k corresponding to the node
                ThreadLocal<?> k = e.get();
                // Equality returns
                if (k == key)
                    return e;
                // If null, trigger a continuous segment cleanup
                if (k == null)
                    expungeStaleEntry(i);
                // Get the next subscript and judge
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * ThreadLocalMap Through this method, we can see that the hash table is a linear detection method to solve conflicts.
         */
        private void set(ThreadLocal<?> key, Object value) {
            // Open a new reference to table
            Entry[] tab = table;
            // Get the length of the table
            int len = tab.length;
             // Get the subscript corresponding to ThreadLocal in the table
            int i = key.threadLocalHashCode & (len-1);

            // Cyclic traversal begins with this subscript
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                // If the same key is encountered, the value is replaced directly.
                if (k == key) {
                    e.value = value;
                    return;
                }

                // If the key has been reclaimed, replace the invalid key
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            
            // Find the empty location, create the Entry object and insert it
            tab[i] = new Entry(key, value);
            // The element size in the table increases by itself
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * Remove key method
         */
        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;
                }
            }
        }
    
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            // Create a reference variable to point to the table
            Entry[] tab = table;
            // Get table length
            int len = tab.length;
            Entry e;

            // Recording Node Subscription for Current Failure
            int slotToExpunge = staleSlot;
            
            /**
             * Starting with the staleSlot subscript, scan forward to find and record the subscript whose front value is null.
             */
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            /**
             * Scanning backwards from the staleSlot subscript
             */
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                
                // Get the ThreadLocal object corresponding to the Entry node
                ThreadLocal<?> k = e.get();

                /**
                 * If it corresponds to the new key, assign value directly and replace the two subscripts i and staleSlot
                 */
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;                    

                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                /* If the current subscript is invalid and no invalid Entry node is found in the backward scan process, slotToExpunge assigns the current location.*/
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If the key is not found in the table, the new Entry is located directly in the current location.
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
        
        /**        
         * The core cleanup function, the main thing it does is
         * 1,Starting with staleSlot, traverse backwards to set null for the value and Entry nodes of the Entry node where the ThreadLocal object is reclaimed, which is convenient for GC, and size decreases by 1
         * 2,The Entry node that is not null will be rehash ed, so long as it is not in the current location, it will move Entry to the next null location.
         * So it's actually a sequential cleanup and rehash operation starting with staleSlot.
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            //Get length
            int len = tab.length;

            // The subscript that will be passed in is null
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
            
            Entry e;
            int i;
            //Traversal deletes all subsequent nodes of the specified node, and ThreadLocal is recycled
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                //Get the key in entry
                ThreadLocal<?> k = e.get();
                // If ThreadLocal is null, then null is set to the value and the position of array subscripts to facilitate GC and size-1.
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {    //If not null
                    //Recalculate the subscript of key
                    int h = k.threadLocalHashCode & (len - 1);
                    
                    // If it's the current location, go through the next one
                    // If it's not the current position, start with i again and find the next coordinate to assign null.
                    if (h != i) {
                        tab[i] = null;
                        
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
        * Clean up recycled Entry
        */
        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];
                // The Entry object is not empty, but if the ThreadLocal key is null, it is cleared
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    // Call cleanup function
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * rehash operation
         */
        private void rehash() {
            expungeStaleEntries();

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

        /**
         * Expansion of tables, because to ensure that the length of tables is a power of 2, the expansion will be doubled.
         */
        private void resize() {
            Entry[] oldTab = table;
            //Old table length
            int oldLen = oldTab.length;
            //New table length
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            /**
             * Starting with subscript 0, iterate backwards one by one to insert into the new table
             * 1,If the key is already null, value sets null to facilitate GC recycling
             * 2,Calculate subscripts by hashcode & len - 1, and insert backward detection by linear detection if the location already has an Entry array
             */
            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++;
                    }
                }
            }

            //Resetting the Threshold of Capacity Expansion
            setThreshold(newLen);
            //Update size
            size = count;
            //Point to the new Entry array
            table = newTab;
        }

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

get method

public T get() {
    // Get the current Thread object
    Thread t = Thread.currentThread();
    // Getting ThreadLocal Map in Thread through getMap
    ThreadLocalMap map = getMap(t);      
    if (map != null) {
        // If the map already exists, take the current ThreadLocal as the key, get the Entry object, and fetch the value from the Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // If the map is empty, the setInitialValue is called for initialization
    return setInitialValue();
}

getMap method

ThreadLocalMap getMap(Thread t) {
    //Return ThreadLocalMap in Thread
    return t.threadLocals;
}

//Thread.java class threadLocals property
ThreadLocal.ThreadLocalMap threadLocals = null;

The ThreadLocalMap reference actually exists in the ThreadLocal class

Entry entity

//Entry is a key-value structure with key as ThreadLocal and value as stored values
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

setInitialValue method

setInitialValue is called when Map does not exist

private T setInitialValue() {
    //Invoke initialValue to generate an initial value, go deep into the initialValue function, and we know that it returns a null.
    T value = initialValue();
    Thread t = Thread.currentThread();
    //Get ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        //If it does not exist, it calls createMap to create ThreadLocalMap
        createMap(t, value);
    return value;
}

void createMap(Thread t, T firstValue) {
    //new a ThreadLocalMap object goes in
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

set method

public void set(T value) {
    //Get the current thread
    Thread t = Thread.currentThread();
    //Get map
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

map.set(this,value) method

   private void set(ThreadLocal<?> key, Object value) {
    
        Entry[] tab = table;
        int len = tab.length;
        //Calculate the position according to the key
        int i = key.threadLocalHashCode & (len-1);

        //Cycle detection
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();

            //If Entry already exists and the key is equal to the incoming key, then a new value is assigned directly to the Entry.
            if (k == key) {
                e.value = value;
                return;
            }

            //If Entry exists, but the key is null, the replaceStaleEntry is called to replace the Entry whose key is empty.
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        //Create an entry
        tab[i] = new Entry(key, value);
        //sz plus 1
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

remove method

 public void remove() {
     //Get map
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         //remove calling map
         m.remove(this);
 }

ThreadLocalMap.remove(this) method

 private void remove(ThreadLocal<?> key) {
     Entry[] tab = table;
     int len = tab.length;
     //Get the index location
     int i = key.threadLocalHashCode & (len-1);
     //Loop traversal table table table table
     for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
         //If found, the call clears the method and ends the loop
         if (e.get() == key) {
             //Call clear() of weakrefrence to clear references
             e.clear();
             //Continuous Segment Clearance
             expungeStaleEntry(i);
             return;
         }
     }
 }

data

Java Problem Collection

Source Code Analysis of ThreadLocal and ThreadLocalMap

Keywords: Java JDK Programming Database

Added by Braet on Wed, 11 Sep 2019 06:09:57 +0300