ThreadLocal is a tool provided in JDK 1.2 by Doug Lea, one of the most familiar authors

The main purpose of this tool is to solve the problem of sharing resources in multiple threads

Let’s take a step back from ThreadLocal’s definition and scenarios

Applicable scenario

  • Scenario 1, where ThreadLocal is used to hold objects that are exclusive to each thread, creates a copy for each thread so that each thread can modify its own copy without affecting other threads’ copies, ensuring thread-safety.

  • Scenario 2, ThreadLocal is used as a scenario where information needs to be kept independently within each thread so that it can be more easily retrieved by other methods. Each thread may get different information. After the previous method holds the information, subsequent methods can get the information directly through ThreadLocal, avoiding passing arguments, similar to the concept of global variables.

Scenario 1

We went to a restaurant and ordered a table of dishes, including noodles, stir-fried dishes and stewed dishes. The chef of this hotel is very enthusiastic, each chef want to eat below for you, the first chef put a salt to this surface, the second chef don’t know also put salt to this surface, the third chef don’t know also put salt to this surface, the fourth chef…….

This is like the problem of thread insecurity in multithreading

So Doug Lea says, each of you make one dish, and don’t mess around

Let’s go ahead and show this simple example (100 threads using SimpleDateFormat)

public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 100; i++) {
        int finalI = i;
        threadPool.submit(new Runnable() {
            @Override
            public void run(a) {
                String date = newThreadLocalDemo01().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); }public String date(int seconds) {
    Date date = new Date(1000 * seconds);
    returndateFormat.format(date); } output:00:05
00:07
00:05
00:05
00:06
00:05
00:05
00:11
00:05
00:12
00:10
  
Copy the code

When you execute the code above, the console prints something different than what you expect

We expect the printed time to be non-repetitive, but you can see that there is a repetition here, for example, the first and third lines are both 05 seconds, which means that something has gone wrong inside.

At this time is there a wit of the students said, the concurrency problem lock not to solve it, that is good idea

Let me change the code to look like this


    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run(a) {
                    String date = newThreadLocalDemo05().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); }public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        String s = null;
        synchronized (ThreadLocalDemo05.class) {
            s = dateFormat.format(date);
        }
        return s;
    }
Copy the code

Well, synchronized is eliminated, but it’s much less efficient

So is there a way to eat watermelon and pick sesame?

You can do this by having each thread have its own simpleDateFormat object, so you can have the best of both worlds


public class ThreadLocalDemo06 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run(a) {
                    String date = newThreadLocalDemo06().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); }public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        returndateFormat.format(date); }}class ThreadSafeFormatter {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue(a) {
            return new SimpleDateFormat("mm:ss"); }}; }Copy the code

Scenario 2

Ok scenario 2 is what we are using in our current project, using ThreadLocal to control data permissions

What we want to achieve is that each thread needs to store information like global variables (such as user information retrieved from interceptors), which can be used directly by different methods without the hassle of passing parameters and not being shared by multiple threads (because different threads get different user information).

For example, you can use ThreadLocal to hold some business content, such as a UserRequest, which holds some information about the user, such as permission group, number, etc

The static ThreadLocal instance gets its set object through its get() method throughout the thread’s life cycle, avoiding the hassle of passing the request as an argument

So we wrote a utility class that looks like this

public class AppUserContextUtil {

    private static ThreadLocal<String> userRequest = new ThreadLocal<String>();

    /** * get userRequest **@return* /
    public static String getUserRequest(a) {
        return userRequest.get();
    }

    /** * set userRequest **@param param
     */
    public static void setUserRequest(String param) {
        userRequest.set(param);
    }

    /** * remove userRequest */
    public static void removeUserRequest(a) { userRequest.remove(); }}Copy the code

So when a request comes in, a thread is responsible for executing the request, no matter how many class methods the request goes through, and can directly get out of our userRequest for business processing or permission control

How are threads stored

Without further ado, above

A Thread has only one ThreadLocalMap, but a ThreadLocalMap can have many ThreadLocal’s, each corresponding to a value.

Since a Thread can call multiple ThreadLocal’s, a Map data structure called ThreadLocalMap is used to store ThreadLocal’s and values.

Let’s take a look at the inner class ThreadLocalMap

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<? >>{
        /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}private Entry[] table;
/ /...
}
Copy the code

The ThreadLocalMap class is a member variable of the Thread class for each Thread, the most important of which is the Entry inner class in this snippet of code. In ThreadLocalMap there will be an array of entries called table. We can think of Entry as a map whose key-value pairs are:

  • Key, current ThreadLocal;
  • Value, the actual variable that needs to be stored, such as the User user object or simpleDateFormat object.

ThreadLocalMap, like a Map, has standard operations like set, GET, rehash, resize, and so on. However, while the idea is similar to HashMap, the implementation is a little different.

One difference, for example, is that HashMap uses a zipper method for hash collisions.

But ThreadLocalMap resolves hash collisions in a different way, using linear probing. If there is a conflict, it does not chain down as a linked list, but continues to find the next empty cell. This is the point where ThreadLocalMap and HashMap handle conflicts differently

Use the pose

The Key to leak

We just introduced threadLocalMaps, and each ThreadLocal has a ThreadLocalMap

Although we might do ThreadLocal Instance = null, we set this instance to NULL and think we can rest easy

However, after GC’s rigorous reachability analysis, the chain of references still exists in the Thread class, even though we set ThreadLocal instances to NULL in the business code.

The GC does a reachability analysis at garbage collection time and finds that the ThreadLocal object is still reachability, so no garbage collection is performed on the ThreadLocal object, causing a memory leak. This leads to OOM, which leads to midnight alarm, which leads to performance 325, which leads to resignation, delivery and so on

Doug Lea considers this dangerous, so the Entry in ThreadLocalMap inherits WeakReference WeakReference,

static class Entry extends WeakReference<ThreadLocal<? >>{

    /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

As you can see, this Entry is extends WeakReference. The nature of weak references is that if the object is only associated with weak references and there is no strong reference association, then the object can be reclaimed, so weak references do not prevent GC. Therefore, this weak-reference mechanism avoids the memory leak problem of ThreadLocal.

The Value leakage

Let’s consider that each Entry of ThreadLocalMap is a weak reference to a key, but this Entry contains a strong reference to a value

Strong reference means that our variable will always be in our memory for as long as the thread lives

But there’s a good chance that we don’t need this variable anymore. Doug Lea is a nice guy to think about this for us. ThreadLocal scans for null entries when it executes its set, remove, rehash, and so on. If the key of an Entry is null, it indicates that its corresponding value is invalid. Therefore, it sets the corresponding value to NULL. In this way, the value object can be reclaimed normally.

But assuming ThreadLocal is no longer in use, the set, remove, and rehash methods are not actually called, and if the thread is still alive and never terminates, the memory will never be GC away, resulting in a value leak. This leads to OOM, which leads to midnight alarm, which leads to performance 325, which leads to resignation, delivery and so on

To avoid this, we should manually call the remove method of ThreadLocal after we have finished using it, in order to prevent memory leaks.

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

The remove method gets the reference to ThreadLocalMap first and calls its remove method. The remove method removes the value associated with the key so that the value can be collected by the GC

summary

That’s all for ThreadLocal in Practice. In this article, we introduce the application scenarios of ThreadLocal and demonstrate the code for these scenarios. Learn how ThreadLocal is stored in threads. You’ve also learned the correct posture to use ThreadLocal.

If this article was helpful to you, please like it and follow it. 🙏


Phase to recommend

3 minutes to understand the bridge mode 11 exceptional best practices from Dachang Factory design patterns, do you know these issues?