directory

  • A quote
  • Two source code analysis
  • Three cases
  • Four summarizes

A quote

The official API of ThreadLocal reads:

* 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. {@code ThreadLocal} instances are typically private * static fields in  classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID).Copy the code

This class provides thread-local variables. These variables are different from normal variables in that each thread accessing one (through its GET or set methods) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in a class that you want to associate state with a thread (for example, a user ID or transaction ID).

When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each thread that uses the variable, so each thread can change its own copy independently. ThreadLocal is defined as private static, and Synchronized is used to share data between threads. ThreadLocal is used for data isolation between threads. ThreadLocal encapsulates getMap(), Set(), Get(), Remove()Copy the code

ThreadLocal contains an array of maps, the key of which is the current thread ID, and the value of which is the object to store.

ThreadLocalMap maintains an array table for each Thread. HreadLocal determines an array subscript that corresponds to the location of the value store, inherited from weak references. Weak references are used to store the mapping between a ThreadLocal and a Value. Weak references are used to solve the problem of the strong binding between a thread and a ThreadLocal, so that if the thread is not collected, the GC will never be able to collect this part of the content.

Two source code analysis

2.1 ThreadLocal
	/ / set methods
	public void set(T value) {
		// Get the current thread
        Thread t = Thread.currentThread();
        // The type of data structure actually stored
        ThreadLocalMap map = getMap(t);
        // If the map is empty, set the current object. No ThreadLocalMap is created
        // And put the values in the create object
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
    }
   
	/ / get methods
    public T get(a) {
    	// Get the current thread
        Thread t = Thread.currentThread();
        // The type of data structure actually stored
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            // The ID of the current thread is passed in to the underlying Map Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}return setInitialValue();
    }   

	/ / remove method
	 public void remove(a) {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if(m ! =null)
             m.remove(this);// Call ThreadLocalMap to remove variables
     }

  	 // The getEntry method in ThreadLocalMap
      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);
        } 

   / / getMap () method
   ThreadLocalMap getMap(Thread t) {
	// A ThreadLocalMap is maintained in Thread
        return t.threadLocals;
    }

	/ / setInitialValue method
	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;
    }
    
    / / createMap () method
   void createMap(Thread t, T firstValue) {
   // Instantiate a new ThreadLocalMap and assign it to the thread member variable threadLocals
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
Copy the code

Set (), get(), and remove() operate on the static inner class ThreadLocalMap. Each new Thread instantiates a ThreadLocalMap and assigns it to the member variable threadLocals. If threadLocals already exists, the existing object is used

ThreadLocal.get()

  • Gets the ThreadLocalMap of the current thread
  • If the Entry of the current ThreadLocal object still exists and returns the corresponding value
  • If ThreadLocalMap is null, the setInitialValue() method is called to prove that it has not been initialized

ThreadLocal.set()

  • Gets the current thread and the corresponding ThreadLocalMap based on the current thread
  • If the corresponding ThreadLocalMap is not null, the set method is called to save the mapping
  • If null, create one and save the K-V relationship

ThreadLocal.remove()

  • Gets the current thread and the corresponding ThreadLocalMap based on the current thread
  • If the corresponding ThreadLocalMap is not null, the remove method in ThreadLocalMap is called to get the current subscript based on key.threadLocalHashCode & (len-1) and remove it
  • Upon success, expungeStaleEntry is called to perform a sequential segment cleanup

2.2 ThreadLocalMap

ThreadLocalMap is an inner class of ThreadLocal

static class ThreadLocalMap {

         /** * create a custom Entry class that inherits from weak references ** let ThreadLocal and store values form key-value relationships ** Use weak references to resolve the strong binding between threads and ThreadLocal * if threads are not collected, The GC will never be able to reclaim the content * */
        static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** * The initial size of the Entry array (the initial length is 16, which is doubled each time) */
        private static final int INITIAL_CAPACITY = 16;

        /** * size as needed * The length must be 2 to the NTH power */
        private Entry[] table;

        ** * The number of entries in The table. ** The number of entries in The table */
        private int size = 0;

        /** * The next size value at which to resize. */
        private int threshold; // Default to 0

        /** * Set the resize threshold to maintain at worst a 2/3 load factor. ** Set the resize threshold to maintain at worst a 2/3 load factor
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /** * Increment I modulo len * nextIndex; /** * Increment I modulo len * nextIndex
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

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

        /** * ThreadLocalMaps constructor * ThreadLocalMaps are lazily constructed, so a */ is created only if at least one node is to be placedThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// An array of internal members, INITIAL_CAPACITY is a constant of 16
            table = new Entry[INITIAL_CAPACITY];
            // Use the bit operation threadLocalHashCode(HashCode) & (length -1) to determine the position of the key-value pair
            // bit operation, the result is the same as taking the mold, calculate the need to store the location
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // Create a new node and store it in the table
            table[i] = new Entry(firstKey, firstValue);
            // Set the table element to 1
            size = 1;
            // Calculate the capacity expansion threshold based on the length
            setThreshold(INITIAL_CAPACITY);
        }

        /** * Construct a new map that contains all inheritable ThreadLocals, only createInheritedMap calls * ThreadLocal itself is thread-isolated and generally does not share or transfer data */
        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++; }}}}/** * getEntry in ThreadLocalMap */
        private Entry getEntry(ThreadLocal
        key) {
        	// Use hashcode to determine the subscript
            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, start at position I and traverse backwards. Based on the linear detection method, it is possible to find it after I
                return getEntryAfterMiss(key, i, e);
        }

      
        /** * Set method of ThreadLocalMap */
        private void set(ThreadLocal
        key, Object value) {
           // create a new reference to table
            Entry[] tab = table;
            // Get the table length
            intlen = tab.length; To get the index value,threadLocalHashCode performs a bit operation (modulo) to get index Iint i = key.threadLocalHashCode & (len-1);
            /** * If a key already exists, update the value * if the key has been reclaimed, replace the invalid key **/
			//
            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 is null, the current invalid Entry node where K resides is replaced
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return; }}// Create a new value if the above traversal does not succeed
            tab[i] = new Entry(key, value);
            // The size of the element in the table increases
            int sz = ++size;
            // Meet the criteria for array expansion x2
            if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }/** * remove the Entry node of the ThreadLocal object from the table */
        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();// Set the reference to null to facilitate GC collection
                    expungeStaleEntry(i);// Start from I
                    return; }}}/** * replaceStaleEntry in ThreadLocalMap */
        private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
            // Create a new reference to table
            Entry[] tab = table;
            // Get the table length
            int len = tab.length;
            Entry e;

           
            // Record the current failed node subscript
            int slotToExpunge = staleSlot;

		   PrevIndex (staleSlot, len); prevIndex(staleSlot, len)
            for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // nextIndex(staleSlot, len
            // occurs first
            for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null;
                 i = nextIndex(i, len)) {
                 // Get the ThreadLocal object corresponding to the Entry nodeThreadLocal<? > k = e.get();// If it is equal to the new key, it is directly assigned to value, replacing the subscripts of I and staleSlot
                if (k == key) {
                    e.value = value;

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

                    // If the previous element exists, start calling cleanSomeSlots to clean it up
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                     /** * Before cleanSomeSlots() is called, *expungeStaleEntry() is called from slotToExpunge to the row where table subscript is *null. The return value is the subscript of table null and len is used to perform a heuristic cleanup. The end result is the method that actually calls expungeStaleEntry * /
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

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

            // If there are other obsolete nodes running, they are cleared and slotToExpunge is reassigned
            if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }/** * expungeStaleEntry() is called in two places * 1. The set method cleans up and rehashed before determining whether resize is required * 2. There will also be a cleanup */
 		 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];
                // Check if the Entry object is not empty
                if(e ! =null && e.get() == null) {
                    n = len;
                    removed = true;
                    // Call this method to recycle,
                    // Clean and rehash the range from I to table where the subscript is nulli = expungeStaleEntry(i); }}while ( (n >>>= 1) != 0);
            return removed;
        }  

        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;
                        while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
        }

       

        /** * 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. */
        private void rehash(a) {
            expungeStaleEntries();

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

        /** * expand the size of the table by 2 */
        private void resize(a) {
        // Get the length of the old table
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            // Create an Entry array twice as long as the old one
            Entry[] newTab = new Entry[newLen];
            // Records the number of valid Entry nodes inserted
            int count = 0;

			 /** * insert into the new table one by one, starting at subscript 0. * Calculate the subscript using hashcode & len-1, and insert into the new table using linear probe if there is already an Entry array
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if(e ! =null) { ThreadLocal<? > k = e.get();if (k == null) {// If the key is already null, set value to null to facilitate GC collection
                        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++; }}}// Reset the capacity expansion threshold
            setThreshold(newLen);
            / / update the size
            size = count;
             // Points to a new Entry arraytable = newTab; }}Copy the code

ThreadLocalMap.set()

  • Key.threadlocalhashcode & (len-1) performs a bit operation on threadLocalHashCode to get the index “I”, the subscript in the table
  • The for loop iterates over, and if the key in the Entry is equal to the ThreadLocal we need to operate on, this directly replaces the assignment
  • If the key is null, replaceStaleEntry() is called to replace it
  • If none of the above conditions are successfully met, create a new value directly in the calculated subscript
  • After a cleanup, call resize() under rehash() to expand

ThreadLocalMap.expungeStaleEntry()

  • This is a core cleanup method in ThreadLocal
  • Why does it need to be cleaned up?
  • In our Entry, if many nodes are obsolete or recycled, but continue to exist in the table array, resources will be wasted
  • When we clear the nodes, we also reorder the following Entry nodes and adjust the Entry size, so that when we value (get()), we can quickly locate resources and speed up the acquisition efficiency of our program

ThreadLocalMap.remove()

  • When we use the remove node, we will use linear detection to find the current key
  • If the current key is the same, the call to clear() points the reference to NULL
  • Perform a sequential cleanup from the “I” position

Three cases

Directory structure:

HttpFilter.java

package com.lyy.threadlocal.config;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class HttpFilter implements Filter {

// Initialize what needs to be done
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}// The core operation is inside this
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
// request.getSession().getAttribute("user");
        System.out.println("Do the filter."+Thread.currentThread().getId()+":"+request.getServletPath());
        RequestHolder.add(Thread.currentThread().getId());
        // Let the request finish, and proceed to the next step
        filterChain.doFilter(servletRequest,servletResponse);


    }

    // Something to do when no longer in use
    @Override
    public void destroy(a) {}}Copy the code

HttpInterceptor.java

package com.lyy.threadlocal.config;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HttpInterceptor extends HandlerInterceptorAdapter {

    // Before interface processing
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle:");
        return true;
    }

    // After interface processing
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHolder.remove();
       System.out.println("afterCompletion");

        return; }}Copy the code

RequestHolder.java

package com.lyy.threadlocal.config;

public class RequestHolder {

    private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();//

    // Provide methods to pass data
    public static void add(Long id){
        requestHolder.set(id);

    }

    public static Long getId(a){
        // The ID of the current thread is passed in to the underlying Map
        return requestHolder.get();
    }

    // Remove variable information, otherwise it will escape and the content will never be released
    public static void remove(a){ requestHolder.remove(); }}Copy the code

ThreadLocalController.java

package com.lyy.threadlocal.controller;

import com.lyy.threadlocal.config.RequestHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/thredLocal")
public class ThreadLocalController {

    @RequestMapping("test")
    @ResponseBody
    public Long test(a){
        returnRequestHolder.getId(); }}Copy the code

ThreadlocalDemoApplication.java

package com.lyy.threadlocal;

import com.lyy.threadlocal.config.HttpFilter;
import com.lyy.threadlocal.config.HttpInterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
public class ThreadlocalDemoApplication extends WebMvcConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(ThreadlocalDemoApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean httpFilter(a){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new HttpFilter());
        registrationBean.addUrlPatterns("/thredLocal/*");


        return registrationBean;
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/ * *"); }}Copy the code

Input: http://localhost:8080/thredLocal/test

Background printing:

Do filter: 35: /thredLocal/test preHandle: afterCompletionCopy the code

Four summarizes

1. A ThreadLocal is a separate storage space for each thread, and each ThreadLocal can hold only one copy of a variable. 2. Compared with Synchronized, ThreadLocal has the effect of thread isolation. Only the corresponding value can be obtained within the thread, and the desired value cannot be accessed outside the thread, which effectively realizes thread closure. When ThreadLocal is used, use its remove() method to remove data and avoid the risk of memory leaks. When we look at the source code, we can’t just do the understanding, but also see the purpose behind the implementation of other features.

Source code address: github.com/839022478/o…