13] What is ThreadLocal? What are the uses? How much do you know?

Posted by nyi8083 on Sun, 26 Dec 2021 15:21:12 +0100

This time, we will introduce the important tool ThreadLocal. The explanation is as follows. At the same time, it introduces what scenario memory leakage occurs, how to reproduce memory leakage, and how to use it correctly to avoid memory leakage.

With questions, let's take a look. If you have any opinions, you can comment and share.

  • What is ThreadLocal? What are the uses?

  • How ThreadLocal is used.

  • ThreadLocal principle.

  • What are the pitfalls and precautions for the use of ThreadLocal.

 1. What is ThreadLocal? What are the uses?

First, the attribute threadLocals in the Thread class is introduced: I understand it this way. It is placed on the Thread. Is it associated with the Thread? What is the association? Guess sharing and private?

ThreadLocal.ThreadLocalMap threadLocals = null;

See the source code and know that it is an instance of each thread

However, we see that there is no method to operate threadlocales. This is a threadLocal. It can be understood that threadLocal is the manager of threadlocales in Thread.

That is, the results of our operations on get, set and remove of threadLocal are all threadLocals save, fetch and delete operations for the current Thread thread instance. Class is inferior to the task of a developer, and the product manager can't control it. The product manager can only assign tasks to developers through the technical leader.

In fact, let's look at the source code of threadLocal. The bottom layer is composed of threadLocalMap, which doesn't match.

To understand the allocated space of this threadLocal.

What are the application scenarios of ThreadLocal?

In fact, we inadvertently use the convenience provided by ThreadLocal all the time. If you are unfamiliar with the switching of multiple data sources, you are familiar with the declarative transactions provided by spring. We use them all the time in the R & D process, and the important implementation basis of spring declarative transactions is ThreadLocal, But we didn't go deep into the implementation mechanism of spring declarative transaction.

Originally, ThreadLocal is so powerful, but it is rarely used by application developers. At the same time, some R & D personnel dare not try ThreadLocal for potential problems such as memory leakage. I'm afraid this is the biggest misunderstanding of ThreadLocal. We will analyze it carefully later. As long as it is used correctly, there will be no problem. If there is a problem with ThreadLocal, isn't spring declarative transaction the biggest potential danger to our program?

Brothers, when programming, I first learn ideas, but I found that I know a ThreadLocal, which involves a lot of knowledge points. Here comes the principle and implementation mechanism of spring declarative transaction. Keep it in mind and you can learn by yourself. I also learn from it later.

2. How to use ThreadLocal

In order to more intuitively experience the use of ThreadLocal, we assume the following scenarios

  1. We generate an ID for each thread.

  2. Once set, the thread life cycle cannot be changed.

  3. Duplicate ID S cannot be generated during a container activity

We create a ThreadLocal management class:

package demo.aqs;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalId {

    private static final AtomicInteger nextId = new AtomicInteger(0);

    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        protected Integer initialValue()
        {
            return nextId.getAndIncrement();
        }
    };


    public static int get() {
        return threadId.get();
    }

    public static void remove() {
        threadId.remove();
    }

    private static void increamentSameThreadId() {
        try{
            for(int i = 0;i < 5; i++) {
                System.out.println(Thread.currentThread()+"_"+i+",threadId:"+ThreadLocalId.get());
            }
        } finally {
            ThreadLocalId.remove();
        }
    }

    public static void main(String[] args) {
        increamentSameThreadId();

        //I'm working on two threads
        new Thread(new Runnable() {
            @Override
            public void run() {
                increamentSameThreadId();
            }
        }).start();

        new Thread(()->{
           increamentSameThreadId();
        }).start();
    }
}

 3.ThreadLocal principle

① ThreadLocal class structure and method resolution:

As can be seen from the above figure, ThreadLocal has three methods: get, set, remove and the internal class ThreadLocalMap

From this figure, we can intuitively see the attribute threadLocals in Thread. As a special Map, its key value is our ThreadLocal instance, and the value value is the value we set.

③ Operation process of ThreadLocal:

getMap(t) returns the threadlocals of the current thread, as shown in the following figure. Then, get the value in ThreadLocalMap according to the current ThreadLocal instance object as the key. If you come in for the first time, call setInitialValue();

The process of set is similar:

4. What are the pitfalls and precautions for the use of ThreadLocal

I often see shocking headlines on the Internet. ThreadLocal leads to memory leakage, which usually makes some developers who do not understand ThreadLocal well at the beginning dare not use it rashly. The more you don't use it, the stranger it is. In this way, we miss a better implementation scheme, so we can make continuous progress only if we dare to introduce new technologies and step on the pit.

Let's see why ThreadLocal causes memory leakage, and what scenarios will cause memory leakage?

First review what is memory leak and what is the corresponding memory overflow

  • Memory overflow: memory overflow. There is not enough memory for the applicant.

  • Memory leak: memory leak. After the program requests memory, it cannot release the requested memory space. The accumulation of memory leaks will eventually lead to memory overflow.

Obviously, the memory is not released due to the non-standard use of TreadLocal.

 

See that the Entry of ThreadLocalMap inherits the WeakReference (weak reference)

Here comes the knowledge:

Since the WeakReference will be recycled in the next gc, why is there no problem with our program?

① So let's test the recycling mechanism of weak references:

package demo;

import java.lang.ref.WeakReference;

/**
 * Weak reference test
 */
public class ThreadLocalLeakTest {

    public static void main(String[] args) {
        String str = new String("weakReference1");
        //Weak application recovery
        WeakReference<String> weakReference = new WeakReference<String>(str);
        System.gc();

        if(weakReference.get() == null) {
            System.out.println("weak1 Recycled");
        } else {
            System.out.println(weakReference.get());
        }

        //There are strong references
        WeakReference<String> weakReference2 = new WeakReference<String>(new String("weakReference1"));
        System.gc();
        if(weakReference2.get() == null) {
            System.out.println("weak2 Recycled");
        } else {
            System.out.println(weakReference2.get());
        }
    }
}

weakReference1
weak2 has been recycled

The recovery of weak references is demonstrated above. Let's take a look at the recovery of weak references in ThreadLocal.

② Weak reference recycling of ThreadLocal

As shown in the above figure, we have no external strong reference to the ThreadLocal object as a key, and the next gc will generate data with null key value. If the thread does not end in time, it will inevitably occur. A strong reference chain Threadref – > thread – > threadlocalmap – > entry, so this will lead to Memory leak.

Let's simulate memory leakage caused by reproducing ThreadLocal:

package com.example.demo.threadLoclLeak;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.springframework.util.ReflectionUtils;

public class TestThreadLoclLeak {

	public static void main(String[] args) throws InterruptedException {

		// In order to reproduce the scene where the key is recycled, we use temporary variables
		ThreadLocalMemory memeory = new ThreadLocalMemory();

		// call
		incrementSameThreadId(memeory);

		System.out.println("GC front: key:" + memeory.threadId);
		System.out.println("GC front: value-size:" + refelectThreadLocals(Thread.currentThread()));

		// Set to null, calling gc does not necessarily trigger garbage collection, but you can manually trigger gc collection through some tools provided by java.
		memeory.threadId = null;
		System.gc();

		System.out.println("GC After: key:" + memeory.threadId);
		System.out.println("GC After: value-size:" + refelectThreadLocals(Thread.currentThread()));

		// Further recycle to ensure that the ThreadLocal instance is recycled
		memeory = null;
		System.gc();

		// The impersonation thread runs all the time
		while (true) {
		}
	}

	/**
	 * Using threadlocal
	 * 
	 * @param memeory
	 */
	private static void incrementSameThreadId(final ThreadLocalMemory memeory) {
		try {
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread() + "_" + i + ",threadId:" + memeory.get().size());
			}
		} finally {
			// Please clear it after use. In order to reproduce the memory leak, it is deliberately not recycled
			// ThreadLocalMemory.remove();
		}
	}

	/**
	 * Use reflection to obtain the value corresponding to ThreadLocal
	 *
	 * @param t
	 * @return
	 */
	public static Object refelectThreadLocals(Thread t) {
		try {
			// Thread
			Field field = ReflectionUtils.findField(Thread.class, "threadLocals");
			field.setAccessible(true);
			Object localmap = ReflectionUtils.getField(field, t);

			// ThreadLocalMap.Entry[]
			Field entryField = ReflectionUtils.findField(localmap.getClass(), "table");
			entryField.setAccessible(true);
			Object[] entry = (Object[]) ReflectionUtils.getField(entryField, localmap);

			List<Object> list = new ArrayList<>();
			for (Object o : entry) {
				if (o != null)
					list.add(o);
			}

			List<Object> result = new ArrayList<>();
			for (Object o : list) {

				// Entry.value
				Field entryValue = ReflectionUtils.findField(o.getClass(), "value");
				entryValue.setAccessible(true);
				Object keyvalue = ReflectionUtils.getField(entryValue, o);
				if (keyvalue instanceof ArrayList) {
					result.add(keyvalue);
				}
			}
			return ((ArrayList<?>) result.get(0)).size();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}

class ThreadLocalMemory {
	// Thread local variable containing each thread's ID
	public ThreadLocal<List<Object>> threadId = new ThreadLocal<List<Object>>() {
		@Override
		protected List<Object> initialValue() {
			List<Object> list = new ArrayList<Object>();
			for (int i = 0; i < 10000; i++) {
				list.add(String.valueOf(i));
			}
			return list;
		}
	};

	// Returns the current thread's unique ID, assigning it if necessary
	public List<Object> get() {
		return threadId.get();
	}

	// remove currentid
	public void remove() {
		threadId.remove();
	}
}

At this point, how do we know that there is a memory leak in memory?

We can use some commands provided by jdk to dump the current heap memory. The commands are as follows:

jmap -dump:live,format=b,file=heap.bin <pid>

Then we use MAT Visual analysis tool , to view the survival status of the memory analysis object instance:

First, open our tooltip for our memory leak analysis:

Here, we can determine the entry of ThreadLocalMap instance Value is not recycled.

Finally, we need to confirm the entry Is key still there? Open the domino tree, search our ThreadLocalMemory, and find no surviving instances.

Above, we reproduce the memory leak caused by improper use of ThreadLocal.

Therefore, we summarized the preconditions for memory leakage when using ThreadLocal

  • The ThreadLocal reference is set to null, and there are no set, get, or remove operations.

  • The thread runs all the time without stopping. (line pool)

  • Garbage collection was triggered. (Minor GC or Full GC)

We can see that the conditions for memory leakage in ThreadLocal are still very harsh, so we can avoid memory leakage by breaking one of the conditions. However, in order to better avoid this situation, we follow the following two small principles when using ThreadLocal:

① ThreadLocal is declared as private static final.

  • Private and final try not to let others modify or change references,

  • Static is represented as a class attribute and will be recycled only at the end of the program.

② Be sure to call the remove method after ThreadLocal is used.

  • The simplest and most effective way is to remove it after use.

above.

Topics: Interview