When reading Spring source code, I noticed that a class ThreadLocal appears many times. In fact, ThreadLocal is widely used, not only in Spring, but also in Mybatis. It may also be seen in the business code of some projects.

In fact, it is a thread-local variable, but because most business programming cases are not used, so we may be unfamiliar, now analyze, so that when we see in the code, it will not hinder our reading.

Use the sample

/ / sample
// Create and assign an initial value
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");

/ / thread 1
new Thread(() -> {
    // Prints the default values
    System.out.println(threadLocal.get());
    / / assignment
    threadLocal.set(Thread.currentThread().getName());
    / / print the value
    System.out.println(threadLocal.get());
}).start();

/ / thread 2
new Thread(() -> {
    // Prints the default values
    System.out.println(threadLocal.get());
    / / assignment
    threadLocal.set(Thread.currentThread().getName());
    / / print the value
    System.out.println(threadLocal.get());
}).start();
Copy the code

The output value:

The above code mainly shows the operations of creating, initializing, getting, and assigning values. It is very simple to use.

The following main analysis of more complex can do, how to achieve the problem.

Analysis of the characteristics of

ThreadLocal: Get set remove initialValue: get Set Remove initialValue: Get Set Remove initialValue

The source code defines it as follows:

This class provides thread-local variables. These variables differ from their normal counterparts because each thread that accesses one (through its GET or set methods) has its own, independently initialized copy of the variable. ThreadLocal instances typically want to bind private static fields in thread-specific classes (for example, user ID or transaction ID).Copy the code

In other words, the main purpose is to share attributes in a thread. How does this work? Take a look at the following sample code to slowly analyze:

/ / sample 2
ThreadLocal<HashMap<String, Object>> threadLocal = ThreadLocal.withInitial(() -> new HashMap<>());

new Thread(() -> {
    HashMap<String, Object> map = threadLocal.get();
    map = map == null ? new HashMap<>() : map;
    map.put("current1", Thread.currentThread().getName());
    threadLocal.set(map);
    System.out.println("t1:" + JSONObject.toJSONString(threadLocal.get()));
}).start();

new Thread(() -> {
    HashMap<String, Object> map = threadLocal.get();
    map = map == null ? new HashMap<>() : map;
    map.put("current2", Thread.currentThread().getName());
    threadLocal.set(map);
    System.out.println("t2:" + JSONObject.toJSONString(threadLocal.get()));
}).start();

System.out.println("main:" + JSONObject.toJSONString(threadLocal.get()));
Copy the code

The output value:

Thread 1 and thread 2 have the same code that prints the same value of the same threadLocal object. There is no interaction between the two threads. In particular, the first sentence of thread 2 (printing the default value), after thread 1 has already operated on the threadLocal object, The printed values are still the default values of the initialization, so there is no mutual contamination between the two threads.

This can also be seen as a feature: variable sharing within threads

The main idea of implementation is that we define a ThreadLocal between multiple threads, each thread will create a copy, each copy is owned by the thread, so that multiple threads do not interfere with each other.

ThreadLocal does not define variables to store values, so where do we store values of sets? First, throw out the concept, and then look at the code:

ThreadLocal only maintains the mapping of values, which are stored in the threadLocals field of the Thread class

Based on the above concept, it is easy to understand that since the values exist in the Thread class of each Thread, it is normal to have isolation between threads.

The following is a concrete way to verify this concept.

Set method

First look at the source code:

public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the object to store the value
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        // if not null, set value
        map.set(this, value);
    else
        // If not initialized, initialized
        createMap(t, value);
}
Copy the code

The following points are involved:

  • GetMap gets the value, from where

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

    As can be seen intuitively, ThreadLocalMap is obtained from Thread.

  • Why does createMap only initialize when it gets a value? What does initialization do

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

    Very simply, a ThreadLocalMap object is created and assigned to threadLocals, where the main input is: the first input to the constructor of ThreadLocalMap is the current object reference of ThreadLocal

  • ThreadLocalMap stores the structure of the value

    ThreadLocalMap is a custom hash map that uses WeakReferences(Java WeakReferences) as a key (Entity extends WeakReference)

    ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Create entry * table is an array
        table = new Entry[INITIAL_CAPACITY];
        // Calculate the subscript and assign the value
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        // Record the number of entries
        size = 1;
        // Adjust the capacity threshold
        setThreshold(INITIAL_CAPACITY);
    }
    Copy the code

    As you can see, the table is created only when ThreadLocalMap has its first value to store, indicating that it is lazily created.

    ThreadLocalMap uses WeakReferences as a key because of the nature of the Entry

    static class Entry extends WeakReference<ThreadLocal<? >> { Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code

    As you can see, Entry extends WeakReference(Java WeakReference), and the constructor that constructs Entry, key, is a ThreadLocal object

    WeakReference related knowledge is involved here, which will not be explained too much here

The get method

public T get(a) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the ThreadLocalMap object
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        // If it is not null, the value is obtained
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            // The associated value is returned if the value is not null
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// Initialize if ThreadLocalMap is empty or the corresponding Entity is empty
    return setInitialValue();
}
Copy the code

GetMap (t) : getMap(t) : getMap(t) : getMap(t) : getMap(t) : getMap(t) : getMap(t)

  • Gets the Entry and the value associated with the Entry

    For example, map.getentry (this) uses a reference to the current ThreadLocal itself.

  • What does the setInitialValue method do

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

SetInitialValue is a variant of set, so it is basically the same as set, but there are still some differences, as follows:

    • Value Specifies the value

      InitialValue here can override itself. When we manually specify the initialValue (we only need to override the initialValue method), if the value is not set at the first fetch, InitialValue is initialized using the return value of our overridden initialValue method, which by default returns NULL.

         protected T initialValue(a) {
             return null;
         }
      Copy the code
    • There is a return value, which is the value returned by initialValue

Now that the main features of ThreadLocal have been shown, let’s examine a few common methods to get a more complete view of the ThreadLocal class.

The remove method

public void remove(a) {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if(m ! =null)
        m.remove(this);
}
Copy the code

The remove method is used to remove the value of the current set. After removing it, the initialValue method is reinitialized by getting it again

The initialValue method

Introduced in the get() method, here are two common methods of overwriting

  • Anonymous inner class
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
    @Override
    protected String initialValue(a) {
        return "base"; }};Copy the code
  • withInitial
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");
Copy the code

WithInitial method

Threadlocal. withInitial overwrites initialValue. Analyze:

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}
Copy the code

SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal = SuppliedThreadLocal

// Extends ThreadLocal
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    // Overrides the initialValue method of ThreadLocal
    @Override
    protected T initialValue(a) {
        returnsupplier.get(); }}Copy the code

SuppliedThreadLocal extends ThreadLocal and overwrites the initialValue method, which corresponds to our analysis above. WithInitial (() -> “baseStr”));

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
Copy the code

Supplier is a functional interface, then

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "baseStr");
Copy the code

Is equivalent to

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() { @Override public String get() { return "baseStr"; }});Copy the code

Now it seems a lot more logical

This has nothing to do with ThreadLocal and is only mentioned to prevent lambda expressions from interfering with reading analysis


Later, some classic use cases will be listed, and the principle analysis will be recorded here.