Learn about java concurrency tool ------------ ThreadLocal

Statement: respect other people's labor achievements. Please attach the original link to reprint! Learning and communication, for reference only!

1, Introduction to ThreadLocal

1. What is ThreadLocal?

We know that in multithreaded programs, multiple threads access the same shared variable, which is easy to cause thread insecurity and concurrency problems. Especially when multiple threads write to the same shared variable, in order to ensure thread safety, we generally need to take some additional synchronization measures when accessing the shared variable to ensure thread safety.

ThreadLocal is provided by the JDK package. It is a concurrent tool. It is a guarantee in addition to the synchronization method of locking and a method to avoid thread insecurity in multi-threaded access. Because each thread accesses its own variables in the thread, there will be no thread insecurity.

2, Two application scenarios

1. Each thread needs a separate object

  • Each Thread has its own instance copy, which is not shared between threads

What does that mean? For a simple example, if there is only one textbook and many students use it, thread safety will be generated if we write notes on the book together. However, if each student copies this textbook and takes notes on his own textbook, won't this avoid thread safety caused by multiple people using the same textbook.

That's what Threadlocal is. Next, show an example in code

Code display

Using multiple threads to access the same tool class (tool classes are generally thread unsafe), because there are multiple threads, in order to avoid the overhead caused by the creation and destruction of each thread, a thread pool is used here.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author delingw
 * @version 1.0
 */
public class FormatTime {
    // Create thread pool
    public static ExecutorService service = Executors.newFixedThreadPool(10);

    // Tool class
    public String date(int secends) {
        Date date = new Date(1000 * secends);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    String s = new FormatTime().date(finalI);
                    System.out.println(s);
                }
            });
        }
    }
}

Operation results

Analysis: it's not difficult to see that every time a thread is created, an instance of SimpleDateFormat will be created, which will consume too much memory. Then the smart little partner will say that it's not easy, just make it shared. This is not possible because it causes thread insecurity.

Code display

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author delingw
 * @version 1.0
 */
public class FormatTime {
    // Create thread pool
    public static ExecutorService service = Executors.newFixedThreadPool(10);
    public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    // Tool class
    public String date(int secends) {
        Date date = new Date(1000 * secends);
        return dateFormat.format(date);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    String s = new FormatTime().date(finalI);
                    System.out.println(s);
                }
            });
        }
        service.shutdown();
    }
}

Operation results

From the above running results, it can be seen that thread insecurity has occurred. Then the smart little partner said that it is not simple. Thread safety has occurred. Let me directly add a lock, which will ensure thread safety,

In fact, if you don't consider the performance problem, this idea is feasible, but it will have a lot of performance overhead. Why? If a lock is added, it means that each thread must wait for the previous thread to release the lock before it can get the lock, which will cause great performance overhead. (there are also multiple threads here)

Code display

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author delingw
 * @version 1.0
 */
public class FormatTime {
    // Create thread pool
    public static ExecutorService service = Executors.newFixedThreadPool(10);
    public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    // Tool class
    public String date(int secends) {
        Date date = new Date(1000 * secends);
        String s = null;
        synchronized (FormatTime.class) {
            s = dateFormat.format(date);
        }
        return s;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    String s = new FormatTime().date(finalI);
                    System.out.println(s);
                }
            });
        }
        service.shutdown();
    }
}

Operation results

To solve this performance problem, we will use our ThreadLocal. Both thread safety and performance are guaranteed.

Code demonstration

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author delingw
 * @version 1.0
 */
public class FormatTime {
	// Thread pool
    public static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String s = new FormatTime().date(finalI);
                    System.out.println(s);
                }
            });
        }
        executorService.shutdown();
    }
    // Tool class
    public String date(int secends) {
        Date date = new Date(1000 * secends);
        // Get value
        SimpleDateFormat format = ThreadlocalClass.threadLocal1.get();
        return format.format(date);
    }
}
/**
 * Both ways are OK
 */
class ThreadlocalClass {
    // Anonymous class
    public static ThreadLocal<SimpleDateFormat> threadLocal1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
    //Lambda expression
    public static ThreadLocal<SimpleDateFormat> threadLocal2 = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
    );

}

2. Global variables need to be saved in each thread

  • Global variables need to be saved in each thread

For example, when we write business, we need to obtain user information in the interceptor (a request is a thread, a thread is a user, and the user information is different between users). Let the current user information be shared by all methods in the thread.

Code display

/**
 * @author delingw
 * @version 1.0
 */
public class ThreadLocalUserInfo {
    public static void main(String[] args) {
        new Service1().process();
    }
}

class Service1 {
    public void process() {
        User user = new User("Zhang San");
        ThreadLocalHodler.userThreadLocal.set(user);
        System.out.println("service1 Got it "+user.name);
        new Service2().process();
    }
}

class Service2 {
    public void process() {
        User user = ThreadLocalHodler.userThreadLocal.get();
        System.out.println("service2 Got it "+user.name);
        new Service3().process();
    }
}

class Service3 {
    public void process() {
        User user = ThreadLocalHodler.userThreadLocal.get();
        System.out.println("service3 Got it "+user.name);
    }
}


class ThreadLocalHodler {
    // User information ThreadLocal
    public static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
}


// User information
class User {
    public String name;

    public User(String name) {
        this.name = name;
    }
}

Operation results

3. Summary of two application scenarios

  • Scenario 1   initialValue

When ThreadLocal get s the object for the first time (delayed loading), the initialization of the object is controlled by us.

  • Scenario 2   set

The objects saved in ThreadLocal are not controlled by us. For example, the user information generated by the interceptor, we can use ThreadLocal.set() to directly put them into our ThreadLocal for our subsequent use

Source code analysis of two application scenarios

It can be seen from the source code that the map.set() method is used to set the value whether it is setInitialValue or set directly
In other words, in the end, it will correspond to an Entry of ThreadLocalMap, but the starting point is different from the Entry


3, Benefits of using ThreadLocal

  • Thread safety achieved

Each thread has its own independent instance and accesses its own.

  • No locking is required to improve execution efficiency

There is no need to lock, which saves the cost of waiting for the lock

  • More efficient use of memory and cost savings

For example, a scenario needs to create an instance every time

  • It eliminates the cumbersome task of transmitting parameters

Either scenario 1 or scenario 2 can be obtained directly through ThreadLocal anywhere. It is no longer necessary to pass the same parameters every time. ThreadLocal makes the code coupling lower and more elegant.

4, Implementation principle and source code analysis

1. ThreadLocal implements the principle of thread independence

From the source code, each Thread has a corresponding Thread object, and the Thread class has a member variable, which is a Map set. The key of the Map set is the reference to ThreadLocal, and the value is the value stored by the current Thread in the ThreadLocal corresponding to the key. When a Thread needs to obtain the value stored in the ThreadLocal variable, the underlying ThreadLocal will obtain the Map set in the Thread object of the current Thread, and then use the ThreadLocal reference as the key to find the value value from the Map set. This is how ThreadLocal implements Thread independence. In other words, ThreadLocal can be Thread independent because the value does not exist in ThreadLocal, but is stored in the Thread object.

Source code analysis

  • ThreadLocalMap class is Thread.threadLocals

ThreadLocalMap class is a variable in the Thread class of each Thread. The most important is a key value pair array Entry[] table, which can be considered as a map key value pair.

  • Key:   Threadlocal reference
  • Value:   ThreadLocal value corresponding to the key.

How this ThreadLocalMap resolves conflicts:

The linear detection method is adopted, that is, if there is a conflict, continue to find the next empty position instead of using the linked list zipper.


2. Common methods source code analysis

  • initialValue()

Source code display

1.   this method will return the "initial value" corresponding to the current thread. This is a delayed loading method, which will only be triggered when calling get(). null is returned by default.

2.   when a thread uses the get() method to access variables for the first time, it will call this method unless the thread calls the set() method first. In this case, this initialValue() method will not be called for the thread.


3.   usually, each thread can call this method at most once, but if remove() has been called, you can call this method again.

4.   if you do not override this method, this method will return null. This method is generally overridden using methods of anonymous inner classes. So that the replica object can be initialized in subsequent use.

  • set()

Set a new value for this thread

Source code display

  • get()

Get the value corresponding to this thread

If this method is called for the first time, initialValue is called to get this value.

Source code display

  • remove()

Delete the value corresponding to this thread

Note: only a single Entry object is deleted here, not the value in the whole ThreadLocalMap.

Source code display

5, Improper use of ThreadLocal, memory leakage and avoidance measures

1. What is a memory leak?

A memory leak means that an object is no longer useful, but the occupied memory cannot be recycled.

2. Improper use, memory leak

  • Characteristics of weak reference

If the object is only weakly referenced and has no strong reference Association, the object will be recycled by GC

Causes of memory leaks

The reason for the memory leak is that the key of the Entry in ThreadLocalMap is a weak reference and the value is a strong reference.

As can be seen from the source code, the Entry in ThreadLocalMap inherits from the weak reference of WeakReference, and each Entry is a weak reference to key, but a strong reference to value.

Under normal circumstances, when the thread terminates, the value saved in ThreadLocal will be garbage collected. However, if the thread does not terminate (such as thread pool), the key may be null and the value may not be null. It is precisely because of this situation that value cannot be recycled. Therefore, OOM may occur.


Solution

JDK has considered this problem, so the set, remove and rehash methods will scan the Entry with null key and set the corresponding value to null, so that the value object will be recycled.

However, if a ThreadLocal is not used, the set, remove and rehash methods will not be called. If the thread does not stop at the same time, it will still cause OOM.

How to avoid memory leakage (ALI protocol)

Calling the remove() method will delete the corresponding Entry object to avoid memory leakage. After using ThreadLocal, you should call the remove() method

6, ThreadLocal considerations

  • Null pointer exception must be set before get, otherwise null pointer exception will occur?

If set is not called before get is called, it will return null.

The reason for the null pointer exception is the NullPointerException exception that may occur during unpacking

Code display

/**
 * @author delingw
 * @version 1.0
 */
public class Demo {
    ThreadLocal<Long> local = new ThreadLocal<>();

    public void set() {
        local.set(10L);
    }
	
	//  ThreadLocal is Long, but the null pointer exception is caused by unpacking
    public long get() {
        return local.get();
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        long l = demo.get();
        System.out.println(l);
    }
}

Operation results

Correct code display

/**
 * @author delingw
 * @version 1.0
 */
public class Demo {
    static ThreadLocal<Long> local = new ThreadLocal<>();

    public void set() {
        local.set(10L);
    }

    public Long get() {
        return local.get();
    }

    public static void main(String[] args) {
    	// Call get directly
        Long l = local.get();
        System.out.println(l);
    }
}

Operation results

Shared object

If what ThreadLocal.set() goes in each thread is the same object shared by multiple threads, such as static object, then the shared object itself obtained by ThreadLocal.get() of multiple threads still has the problem of concurrent access.

Do not use it forcibly

If you can solve the problem without ThreadLocal, don't force it. For example, when the number of tasks is small, you can create a new object in the local variable to solve the problem, so you don't need to use ThreadLocal. Because we may forget to call the remove() method, resulting in memory leakage.

Tags: Java Multithreading source code analysis

Posted on Fri, 12 Nov 2021 00:32:30 -0500 by sr20rps13