Usage scenario and mode of ThreadLocal

Posted by flyankur on Tue, 18 Jan 2022 14:38:46 +0100

Two usage scenarios - the purpose of ThreadLocal

Typical scenario 1: each thread needs an exclusive object (usually a tool class. The typical classes used are SimpleDateFormat and Random)

Typical scenario 2: global variables need to be saved in each thread (for example, obtain user information in the interceptor), which can be used directly by different methods to avoid the trouble of parameter transmission.

Typical scenario 1: each thread needs an exclusive object

Each Thread has its own instance copy, which is not shared;

Example: SimpleDateFormat. (when multiple threads share such a SimpleDateFormat, but this class is not safe)

  • Two threads use their own SimpleDateFormat, which is no problem;

  • Later, 10 threads and 10 simpledateformats were extended. Although the writing method is not elegant, it is barely acceptable

  • However, when the demand becomes 1000, the thread pool must be used, which consumes too much memory;

  • However, we need to create every simpledateformat. It takes too much time to change the new object to static, and all threads share a simpledateformat object. However, this is unsafe for threads and is prone to time consistency. It can be solved by locking when calling, but it is still not elegant;

  • ThreadLocal is used to solve this problem. Each thread is assigned a simpledateformat, but this ThreadLocal is safe;

package threadlocal;

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

/**
 * Description: using ThreadLocal, each thread is allocated its own dateFormat object to ensure thread safety and efficient use of memory
 */
public class ThreadLocalNormalUsage05 {

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

    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() {
                    String date = new ThreadLocalNormalUsage05().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //The unit of the parameter is milliseconds, timed from 1970.1.1 # 00:00 # GMT
        Date date = new Date(1000 * seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}

Typical scenario 2: the current user information needs to be shared by all methods in the thread

  • A cumbersome solution is to pass user as a parameter layer by layer, from service-1() to service-2(), and so on, but doing so will lead to code redundancy and difficult maintenance.

  • The advanced point is to use userMap to save, but when multiple threads work at the same time, you need to ensure thread safety. You need to use synchronized, or concurrenthashmap , but whatever you use will have an impact on performance

Global variables need to be saved in each thread, which can be used directly by different methods to avoid the trouble of parameter passing

  • use ThreadLocal Save some business memory (user permission information, user name, userId, etc. obtained from the user system)

  • This information is the same in the same thread, but different threads use different business contents

  • During the thread life cycle, you can get the object you set through the get() method of this static ThreadLocal instance, avoiding the trouble of passing this object as a parameter

package threadlocal;

/**
 * Description: demonstrate ThreadLocal usage 2: avoid the trouble of passing parameters
 */
public class ThreadLocalNormalUsage06 {

    public static void main(String[] args) {
        new Service1().process("");

    }
}

class Service1 {

    public void process(String name) {
        User user = new User("Super brother");
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}

class Service2 {

    public void process() {
        User user = UserContextHolder.holder.get();
        ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println("Service2 Get user name:" + user.name);
        new Service3().process();
    }
}

class Service3 {

    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3 Get user name:" + user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();


}

class User {

    String name;

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

Note:

  • It emphasizes the sharing of different methods in the same request (in the same thread);

  • There is no need to override the initialValue() method, but the set() method must be called manually

ThreadLocal method Usage Summary

Scenario 1: initialValue

When ThreadLocal get s the object for the first time, the initialization time of the object can be controlled by us.

Scenario 2: set

If we need to save the object in ThreadLocal, the generation time is not controlled by us at will. For example, for the user information generated by the interceptor, use ThreadLocal Set is directly put into ThreadLocal.

ThreadLocal principle

Clarify Thread, ThreadLocalMap and ThreadLocal

Introduction to main methods

  • T initialValue(): initialization

  • Void set (T): sets a new value for this thread

  • T get(): get the value corresponding to this thread. If this is the first call to get(). initialize will be called to get this value

  • void remove(): delete the value obtained by this thread

After the ThreadLocalMap conflicts, the linear detection method is used. In addition, welcome to the official account Java shrimp, the background reply to the "back-end interview", to send you an interview question treasure!

ThreadLocal usage problem memory leak

What is a memory leak

An object is no longer useful, but the memory occupied cannot be recycled.

Leakage of Value

  • Each Entry in ThreadLocalMap is a weak reference to key, and each Entry contains a strong reference to value.

  • Normally, when the thread terminates, the value saved in ThreadLocal will be garbage collected because there is no strong reference.

  • However, if the thread does not terminate (for example, the thread pool needs to be maintained for a long time), the value corresponding to the key cannot be recycled. Thread - > threadlocalmap - > entry (key is null) - > value

  • Because there is a strong reference link between value and Thread, value cannot be recycled, and OOM may occur; JDK has considered this problem, so in the set, remove and rehash methods, the key will be scanned to be null, and the value will also be set to null, so that the value object can 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, the call chain will always exist, resulting in memory leakage of value.

How to avoid memory leakage?

  • Calling the remove method will delete the corresponding Entry object to avoid memory leakage. Therefore, after using ThreadLocal, you should call the remove method.

Practical application scenario - Example Analysis in spring

  • DateTimeContextHolder: ThreadLocal is used

  • RequestContextHolder: ThreadLocal is used

Topics: Java Spring