When I first started using C#, I studied its ThreadLocal and LogicalCallContext (ExecutionContext) that could be passed across threads, but C# is not open source (fortunately, it is. Net Core), can only go around the world to find documents, find blogs. When I switched to Java, I finally came across another way to investigate a problem: rather than looking up data, I could look at the code and debug it. Then, everything became less mysterious.

Function and core principle

In my opinion, Thread Local provides two main functions:

  1. Easy to transfer parameters. Provide a convenient “inventory shelf” where you can store and fetch whenever you want, without passing a bunch of arguments to each layer of method calls. (We tend to store public data on shelves.)
  2. Thread isolation. The values of each thread are not coherent, shielding the trouble of multithreading.

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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)

The code is too well commented. ThreadLocal should be translated as thread-local variable, meaning as opposed to normal variables. ThreadLocal is normally a static variable, but its get() values are unrelated to each thread.

ThreadLocal has several core methods:

  • get()Get the value of the variable. If this ThreadLocal has been set in the current thread, this value is returned; Otherwise, call indirectlyinitialValue()Initialize a variable in the current thread and return its initial value.
  • set()Sets variable values in the current thread.
  • protected initialValue()Initialization method. The default implementation returns NULL.
  • remove()Deletes a variable from the current thread.

The principle is briefly

  • Each thread has a threadLocals property of type ThreadLocalMap, which acts as a Map, the key is the ThreadLocal itself, and the value is the value we set.
public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
Copy the code
  • When we go throughthreadLocal.set("xxx");You put a key-value pair in the threadLocals property of the thread, where key is the current thread and value is the value you set.
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! = null) map.set(this, value);else
        createMap(t, value);
}
Copy the code
  • When we use threadlocal.get(), we get the value of the thread set based on the current thread as the key.
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this);if(e ! = null) { @SuppressWarnings("unchecked")
             T result = (T)e.value;
             returnresult; }}return setInitialValue();
}
Copy the code

Core: ThreadLocalMap

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

ThreadLocalMap is a custom Hash map that uses open addressing to resolve conflicts.

  • Its Entry is oneWeakReferenceInherited, to be exactWeakReference
  • A reference to a ThreadLocal object is passedWeakReferencethereference,entry.get()Is treated as a key for a map element, and Entry has an extra fieldvalue, which holds the actual value of the ThreadLocal variable.
  • Because it is a weak reference, if a ThreadLocal object no longer has a normal reference, the ThreadLocal object is cleared when GC occurs. And the key of the Entry isentry.get()Will become null. However, the GC only clears the referenced object,EntryIt is also referenced by the thread’s ThreadLocalMap and therefore will not be cleared. As a result,valueThe object will not be cleared. Unless the thread exits, causing the thread’s ThreadLocalMap to be released as a wholevalueCan’t free up memory,A memory leak!
  • The JDK authors naturally thought of this, so in many of ThreadLocalMap’s methods, callexpungeStaleEntries()removeentry.get() == nullElement to release the value of the Entry. So, as long as the thread is using another ThreadLocal, the stale ThreadLocal memory will be cleared.
  • However,For most of our usage scenarios, ThreadLocal is oneA static variable, so there is always a normal reference to this entry of ThreadLocalMap in each thread. So the ThreadLocal Entry is never released, naturallyexpungeStaleEntries()There’s nothing I can do,valueMemory will not be freed. So when we do run out of ThreadLocal, we can actively invoke itremove()Method, actively delete entry.

However, is it really necessary to call the remove() method? Usually our scenario is on the server side, where threads are constantly processing requests, and each incoming request causes the Thread Local variable in a Thread to be assigned a new value, and the original value object naturally loses reference and is cleaned up by the GC. So when using static Thread Local and not setting it to NULL, there is no leak!

Cross thread passing

Thread Local cannot be passed across threads. But there are some scenarios where we want to deliver. Such as:

  1. Start a new Thread to execute a method, but expect the new Thread to also get the context (e.g., User ID, Transaction ID) owned by the current Thread via Thread Local.
  2. When you submit a task to a Thread pool for execution, you want the Thread that will execute the task to inherit the Thread Local of the current Thread, so you can use the current context.

Let’s take a look at some of them.

InheritableThreadLocal

InheritableThreadLocal Is a class that inherits ThreadLocal and overrides three methods.

Public class InheritableThreadLocal<T> extends ThreadLocal<T> {return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value forthe initial entry of the table. */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}Copy the code

When using InheritableThreadLocal, the map uses the thread’s inheritableThreadLocals field instead of the threadLocals field.

The inheritableThreadLocals field is called inheritable and will be passed when a new thread is created. The code is in the init() method of Thread:

if(inheritThreadLocals && parent.inheritableThreadLocals ! = null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);Copy the code

By now, inheritableThreadLocals allows us to pass values from ThreadLocal to child threads when the parent thread creates them, which already satisfies most needs [1]. However, there is a serious problem that occurs when threads are reused [2]. For example, when inheritableThreadLocals is used to transfer values from thread pools, the inheritableThreadLocals will only transfer values when new threads are created. Thread reuse does not do this.

There’s nothing the JDK can do about it. C# provides LogicalCallContext (and Execution Context) to solve this problem. Java has to extend the thread class itself to implement this functionality.

If there is any issues with ali open-source transmittable-thread-local

Making the address.

If there is any transmittable-thread-local, there are three types of transmittable thread-local

  1. Modify Runnable and Callable
  2. Modify thread pool
  3. Java Agent to modify (run time modify) the JDK thread pool implementation class.

The official documentation is very clear on how to use it.

The following is a brief analysis of the principle:

  • To solve the thread local transfer problem when using thread pools, you pass the current ThreadLocal value at the time the task is submitted to the thread at the time the task is executed.
  • The ThreadLocal of the current thread is captured before the task is committed, saved, and then replayed in the target thread when the task is executed.

Code level to modify Runnable for example:

  1. Always call TtlRunnable() first when creating itcapture()Capture ThreadLocal in the current thread
private TtlCallable(@Nonnull Callable<V> callable, boolean releaseTtlValueReferenceAfterCall) { this.capturedRef = new AtomicReference<Object>(capture()); . }Copy the code
  1. capture()Method isTransmitterClass static methods:
public static Object capture() { Map<TransmittableThreadLocal<? >, Object> captured = new HashMap<TransmittableThreadLocal<? >, Object>();for(TransmittableThreadLocal<? > threadLocal : holder.get().keySet()) { captured.put(threadLocal, threadLocal.copyValue()); }return captured;
}
Copy the code
  1. inrun()The ThreadLocal that was captured before is released.
public void run() { Object captured = capturedRef.get(); . Object backup = replay(captured); try { runnable.run(); } finally { restore(backup); }}Copy the code

Sequence diagram:

application

  • Spring MVC’s static class RequestContextHolder, getRequestAttributes() actually gets the value of InheritableThreadLocal

    in the current thread. You can also say that it can be passed to a thread that it creates, but not to an existing thread.

    As for what it is set to, please refer to its comments: Holder class to expose the web request in the form of a thread-bound RequestAttributes object. The request will be inherited by any child threads spawned by the current thread if the inheritable flag is set to true. Use RequestContextListener or org.springframework.web.filter.RequestContextFilter to expose the current web request. Note that org.springframework.web.servlet.DispatcherServlet already exposes the current request by default.

  • Database connections in Spring, sessions in Hibernate.

  • Several application scenarios summarized by Alibaba TTL

.

Some of the pit

Good article:Talk about the design and limitations of ThreadLocal

  • Two considerations for ThreadLocal design are discussed, and how ThreadLocal is solved.
    1. How to release resources when Thread exits, avoiding memory leaks.
    2. Map data may be accessed by multiple threads, resulting in resource competition. Concurrent synchronization needs to be considered.
  • Reference to memory leaks where TheadLocal is gc but its associated Entry is still in Lucene:
    1. When I look at ThreadLocal, I’m also thinking, since the Entry key isWeakReferenceWhy not make Value as wellWeakReferenceSo it doesn’t leak out?
    2. On second thought, if a value is a weak reference, there is no guarantee that it will still be there when it is used, because it will be discarded by gc.
    3. Lucene saved another one to solve the problemWeakHashMap<Thread, T>, so that the value will not be cleared as long as the thread is alive.
    4. However, it also brings the problem of multi-threaded access, which needs to be locked.

You see, everything is not perfect. We just have to get that balance right.