This is a common and easy-to-use technique for dealing with concurrency problems on a daily basis. There are times when ThreadLocal is particularly useful for solving problems, both within frameworks and in normal business development.

ThreadLocal (also known as thread-local variables). Literally, each thread has a copy of its local variables, and variables from each thread do not interfere with each other. In contrast to the way many threads synchronize blocks of code execution, ThreadLocal uses a space-for-time approach to ensure thread-safety: if we ensure that each thread accesses its own variables (thread resource isolation), there is no shared concurrency problem. With ThreadLocal, we can create our own cache for each thread, and then pull any variables we need in the thread from that cache.

Simple to use

The source code is annotated with a demo:

public class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);
    // Generally defined as private static in a class for easy reference
    private static final ThreadLocal<Integer> threadId =
        new ThreadLocal<Integer>() {
            @Override protected Integer initialValue(a) {
                returnnextId.getAndIncrement(); }};// Returns the current thread's unique ID, assigning it if necessary
    public static int get(a) {
        returnthreadId.get(); }}Copy the code

This Demo assigns an increment ID to each thread.

  • When we need to use variables and objects behind the thread, we use the defined ThreadLocalsetMethod to save the value locally in the current thread;
  • Called when neededgetMethod acquisition;
  • If it is determined that it is no longer in use, pass it againremoveMethod to remove. This is an overlooked but important step.

The principle of

Why?

First of all, why do we use ThreadLocal?

To set up A scenario, we have A huge chain of business logic, method A-B-C-D-e-f-…. . At this point, A creates an object XX, and that object needs to be given to B, and B needs to be given to C, and C needs to be given to D. We are going to add a parameter XX to each of these methods. It must be annoying for you too! So in A single-threaded environment, we must define A global variable XX, and after the assignment of A, BCD and so on, we can get all of these directly from XX.

But in multithreading, there is a concurrency safety issue, two or more threads to each other assignment and fetch, when the result is bound to be a problem.

So here’s the question:

  1. I want to be global, not take arguments on every method.
  2. I think this is safe in a multi-threaded environment. What do I do?

If you don’t have ThreadLocal, you might define XX as a Map, where the key holds the thread ID, and then stores data into the Map, fetching data as needed. Java gets this requirement for you and creates the ThreadLocal class for you. You don’t need to do this. The JDK does it for you.

Applicable scenario

In my opinion, there are two applicable scenarios in general business development:

  1. For exclusive object security updates: each thread creates its own independent copy, and then changes the property values of the copy during subsequent business operations, without affecting other threads.
  2. For exclusive object global use: At some point in the business logic, an object is created that you want to use in many subsequent methods, but you don’t want to pass method parameter names down the hierarchy.

Note: ThreadLocal does not satisfy all concurrency scenarios, so don’t abuse it to solve all concurrency problems.

Here are two specific examples:

Scenario 1:

  • When using multiple data sources in a project, we use reflection annotations to get to which data source the current data source should be cut to. We can define a ThreadLocal

    > to store the data source switching order of the current thread: Example: DataSourceA -> DataSourceB -> DataSourceC
  • If you don’t use ThreadLocal, you’re out of order, and another thread cutting affects the data source judgment of the current thread.

Scenario 2:

  • This scenario is the most used. For example, SimpleDateFormat in Java is not thread-safe, and we can create our own SimpleDateFormat object for each thread if we want to use it. Spring transaction management, for example, puts a database Connection into ThreadLocal before the transaction logic, and all subsequent operations use that Connection.
  • This kind of scenario is really more about using a thread-unsafe utility class and not modifying it.

As you can see, ThreadLocal is not about shared security of objects. ThreadLocal is of little help in such scenarios. We have concurrent scenarios where threads interact with shared objects (such as counting visits to a web interface, incrementing only a shared variable) that are not ThreadLocal service scenarios. The ThreadLocal service must have exclusive variables.

In my opinion, the ergodicity of using ThreadLocal is that it prevents an object from having to make a long chain of arguments in a method chain called in a multi-threaded environment.

Source code analysis

These are the same methods we typically use with ThreadLocal, which hides the underlying implementation details of how to keep threads safe. The source code for ThreadLocal is relatively simple, with the main structure as follows:

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
  	// Core method
    public ThreadLocal(a) {}
    public T get(a) {}
    public void set(T value) {}
    public void remove(a) {}
  	// Core inner class
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}}}Copy the code

We can see that ThreadLocal provides only a few methods and an inner class. And it itself doesn’t hold any objects. So the key point is to look at the commonly used get and set methods:

Set method

public void set(T value) {
 // Get the ThreadLocalMap reference from the current thread.
 // Create a Map object in the current thread. If there is a Map object in the current thread, set it directly. If there is no Map object in the current thread, initialize a Map object and put the value into it.
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);// Pass value as value with ThreadLocal as key
    else
        createMap(t, value);
}
Copy the code

We simply get the currentThread object with thread.currentthread (), and then get a ThreadLocalMap object with the value in the map with getMap. What does getMap do?

ThreadLocalMap getMap(Thread t) {
    // Only one attribute of Thread is returned
    return t.threadLocals;
}
Copy the code

GetMap role is to get the Thread attribute values in the Thread: ThreadLocal. ThreadLocalMap threadLocals = null. That is, the class of a ThreadLocalMap is defined in a ThreadLocal, but the actual map is defined in a Thread object. This map is held by the Thread object, which is why it is called a thread-local variable. The map is a cache that is private to the Thread.

The get method

public T get(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // Find the value of the ThreadLocal object in the current thread Map
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// Do not initialize it
    return setInitialValue();
}
Copy the code

The remove method

public void remove(a) {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if(m ! =null)
        // Map removes the key value of the ThreadLocal object
        m.remove(this);
}
Copy the code

The get and remove methods are simple, normal map operations. So the focus of ThreadLocal is on this ThreadLocalMap.

ThreadLocalMap

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}private static final int INITIAL_CAPACITY = 16;
    private Entry[] table;
    private int size = 0;
    private int threshold; // Default to 0ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {}private ThreadLocalMap(ThreadLocalMap parentMap) {}
    
    private void set(ThreadLocal
        key, Object value) {}
    private void remove(ThreadLocal
        key) {}
    private static int nextIndex(int i, int len) {}
    private static int prevIndex(int i, int len) {}

	private void setThreshold(int len) {}
    private void rehash(a) {}
    private void resize(a) {}}Copy the code

I won’t go into the details of how to implement this Map, but it is understood that we made a small HashMap and made some optimizations for ThreadLocal itself.

The illustration

The diagram above illustrates the relationship between threads and ThreadLocal, which leads to a number of basic conclusions:

  • Threads hold a Map of local variables whose keys are ThreadLocal objects (you can see why they are called thread-local variables).
  • If you have a variable that you want to put into a ThreadLocal variable you need to create a ThreadLocal object;
  • ThreadLocal itself has no storage structure, only methods, so variables are not stored in a ThreadLocal, which handles them.

A memory leak

What is a memory leak?

A Memory Leak refers to a program that fails to release or release dynamically allocated heap Memory for some reason, resulting in a waste of system Memory, slowing down the program and even crashing the system. [Baidu Baike]

Simply put, it means that the memory you have applied for is no longer used by the program, but has not been released, resulting in a waste of memory. For languages like C++ that don’t have GC, programmers need to manually call destructors to actively free memory. But generally speaking, GC languages do not have the problem of memory leakage, and all object free operations should be done by GC, without us to manually free. However, ThreadLocal can cause the GC to fail to properly reclaim objects that are no longer used because of the low-level masking of our upper layer. Here is an example of a memory leak in ThreadLocal.

Memory Leak Example

public class ThreadLocalMemoryLeakDemo {

    static class LocalVariable {
        // Build a large object to monitor memory
        private Long[] value = new Long[1024 * 1024];
    }

    final static ThreadPoolExecutor EXECUTOR = ThreadUtil.newExecutor(5.5);

    final static ThreadLocal<LocalVariable> LOCAL_VAL_HOLDER = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
      	// Submit 50 tasks, each using ThreadLocal for set and GET operations
        for (int i = 0; i < 50; i++) {
            EXECUTOR.execute(() -> {
                LocalVariable localVariable = new LocalVariable();
                localVariable.value[0] = Thread.currentThread().getId();
                LOCAL_VAL_HOLDER.set(localVariable);
                System.out.println(Thread.currentThread().getId() + "Using local variables" + LOCAL_VAL_HOLDER.get().value[0]);
                // Verify the difference between each use of remove and each use of no remove
                //LOCAL_VAL_HOLDER.remove();
            });
          	// Add sleep time to monitor JVM memory fluctuations
            Thread.sleep(1000);
        }
        System.out.println("END"); }}Copy the code

Description:

Here we create a pool of five fixed threads and submit 50 tasks to the pool by using local_val_holder.remove () after each use of ThreadLocal; Clear and do not use these two ways to execute the program, compare the two running memory to analyze whether the memory leak.

  • There is no heap memory state where the remove method is called

  • The heap memory state where the remove method was called

Observation shows that there is no useremoveAfter the system GC and my manual GC, the resident memory is about 25M. While the use of theremoveIs very low after GC. This means that some memory is not GC, which is a memory leak!

Why the memory leak?

The key is the ThreadLocal object, and the value is the object we need to use later (again, for example XX).

In normal Java code, after creating a XX and using it, GC starts from Root to discover the reachabability of objects in the heap, finds that XX is not referenced, and will reclaim XX. But with ThreadLocal, things are different. The following illustration illustrates why XX cannot be GC when ThreadLocal is used (this illustration has been adjusted for ease of understanding and does not represent the actual situation, but does not affect understanding of the principle).

  1. hereThread1 StackStack equivalent to Thread1, one of the threads in the thread pool. We can understand that a Thread is also an object in the heap.
  2. Thread holds a map [06]. The lifetime of the map is the same as the lifetime of the Thread. Since the Thread pool is used and there is no shutdown, the map will actually remain in the heap. [05-06 this reference chain is the same as the thread pool life cycle]
  3. At this point, we create an Object: XX, namely the above relation line [01]. Putting XX into a Thread map with ThreadLocal results in two new references [02-03]. This is the key to memory leaks.
  4. At this point we assume that the Thread1 task has completed, and the [01] chain will break. Normally, we would think that XX has no place to reference it, but in fact there is [03] chain, and [03] chain because [05-06] these two chains are the same as the life cycle of the thread pool, so it also exists!
  5. So memory leaks are caused by the following chain of references:

Thread Pool > Thread Ref > Thread > ThreaLocalMap > Entry > value: 05 > 06 > 03

For Java programmers, because there is little attention paid to object collection, all objects are safely handed over to the garbage collector. I personally think this is the main cause of memory leaks 🐶. ThreadLocal, however, falls through the cracks. We may no longer use a ThreadLocal to set an object in our program, but the thread will always hold a reference to that object.

In summary, the real reason for the memory leak is that the thread also refers to the object being created internally, and this reference layer is transparent to us! Calling the remove method references this to the clean, so ThreadLocal will not leak memory if used properly.

Why weak references instead of strong references

[02] This reference is dashed because the Entry in the ThreadLocalMap holds a weak reference to the ThreadLocal object (which is immediately reclaimed as soon as GC is done) :

static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {// k weakly references the Thread object passed by the construct
        super(k); value = v; }}Copy the code

So why use weak references? Let’s take a stand before we say this:

Memory leaks are a corollary to improper use of ThreadLocal and have nothing to do with weak references. Weak references are an optimization for ThreadLocal memory leaks.

(AT that time, I did not read carefully in order to prepare for the interview, but it was very good to catch these keywords, I somehow connected the memory leak with weak reference 😂.)

  • First, with strong references, not only can a value not be freed! The Entry key cannot be released even if it is used up! That is, the ThreadLocal object of new cannot be reclaimed.
  • And then,Using weak references has several benefits:
    • When the external ThreadLocal is no longer needed, the next GC will collect it directly, reducing memory leaks on the ThreadLocal side.
    • When a ThreadLocal object is reclaimed, the Entry key is null. ThreadLocal is then optimized to remove values with null keys the next time ThreadLocalMap calls the set(), get(), and remove() methods. One caveat: The get, set, and remove operations are triggered when you operate externally on other ThreadLocal objects.
    • So GC makes the key null, so that the next time the map does an operation, it can remove useless values. This further reduces the amount of memory leaks.

To sum up: Use weak references just to reduce the amount of memory leaks! But he still couldn’t fix the memory leak. So when we use ThreadLocal, we still have to call remove after we run out of ThreadLocal to avoid memory leaks.

The important thing to remember with ThreadLocal is to call remove when we have finished using thread-local objects. To avoid memory leaks.

“Reference”

www.jianshu.com/p/d225dde8c…

Blog.csdn.net/Y0Q2T57s/ar…