Home  >  Article  >  Java  >  How to use Java ThreadLocal class

How to use Java ThreadLocal class

王林
王林forward
2023-05-14 18:49:06994browse

    As shown in the picture:

    How to use Java ThreadLocal class

    ##Quick Start

    Next, we will use a simple example to show you the basic usage of ThreadLocal

    package cuit.pymjl.thradlocal;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/1 10:56
     **/
    public class MainTest {
        static ThreadLocal threadLocal = new ThreadLocal<>();
    
        static void print(String str) {
            //打印当前线程中本地内存中本地变量的值
            System.out.println(str + " :" + threadLocal.get());
            //清除本地内存中的本地变量
            threadLocal.remove();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread1 local variable");
                    //调用打印方法
                    print("thread1");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread2 local variable");
                    //调用打印方法
                    print("thread2");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            t1.start();
            t2.start();
        }
    }

    The running results are as shown in the figure:

    How to use Java ThreadLocal class

    The principle of ThreadLocal

    ThreadLocal related class diagram

    Let’s first take a look at the class diagram structure of the ThreadLocal related class, as shown in the figure:

    How to use Java ThreadLocal class

    As can be seen from this figure, there is a threadLocals and an inheritableThreadLocals in the Thread class. They are both variables of the ThreadLocalMap type, and ThreadLocalMap is a customized Hashmap. By default, these two variables in each thread are null, and they are created only when the current thread calls the set or get method of ThreadLocal for the first time. In fact, the local variables of each thread are not stored in the ThreadLocal instance, but in the threadLocals variable of the calling thread. In other words, local variables of type ThreadLocal are stored in specific thread memory spaces. ThreadLocal is a tool shell that puts the value into the threadLocals of the calling thread through the set method and stores it. When the calling thread calls its get method, it takes it out from the threadLocals variable of the current thread for use. If the calling thread never terminates, then this local variable will always be stored in the threadLocals variable of the calling thread. Therefore, when the local variable is not needed, you can delete the local variable from the threadLocals of the current thread by calling the remove method of the ThreadLocal variable. In addition, why are the threadLocals in Thread designed as a map structure? Obviously because each thread can be associated with multiple ThreadLocal variables. Next, let's take a look at the source code of ThreadLocal's set, get, and remove

    set

        public void set(T value) {
            // 1.获取当前线程(调用者线程)
            Thread t = Thread.currentThread();
            // 2.以当前线程作为key值,去查找对应的线程变量,找到对应的map
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 3.如果map不为null,则直接添加元素
                map.set(this, value);
            } else {
                // 4.否则就先创建map,再添加元素
                createMap(t, value);
            }
        }
        void createMap(Thread t, T firstValue) {
            /**
             * 这里是创建一个ThreadLocalMap,以当前调用线程的实例对象为key,初始值为value
             * 然后放入当前线程的Therad.threadLocals属性里面
             */
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        ThreadLocalMap getMap(Thread t) {
            //这里就是直接获取调用线程的成员属性threadlocals
            return t.threadLocals;
        }

    get

        public T get() {
            // 1.获取当前线程
            Thread t = Thread.currentThread();
            // 2.获取当前线程的threadlocals,即ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            // 3.如果map不为null,则直接返回对应的值
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 4.否则,则进行初始化
            return setInitialValue();
        }

    The following is the code for

    setInitialValue ##

    private T setInitialValue() {
        //初始化属性,其实就是null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //通过当前线程获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果map不为null,则直接添加元素
        if (map != null) {
            map.set(this, value);
        } else {
            //否则就创建,然后将创建好的map放入当前线程的属性threadlocals
            createMap(t, value);
        }
            //将当前ThreadLocal实例注册进TerminatingThreadLocal类里面
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal) this);
        }
        return value;
    }
    I need to add some explanation here

    TerminatingThreadLocal

    . This class is new in jdk11 and does not exist in jdk8, so there is no relevant description of this class in many source code analyzes on the Internet. I took a look at the source code of this class, and its function should be to avoid the problem of ThreadLocal memory leaks (if you are interested, you can take a look at the source code, and please correct me if there are any errors). This is the official explanation:

    /**
     * A thread-local variable that is notified when a thread terminates and
     * it has been initialized in the terminating thread (even if it was
     * initialized with a null value).
     * 一个线程局部变量,
     * 当一个线程终止并且它已经在终止线程中被初始化时被通知(即使它被初始化为一个空值)。
     */
    remove

         public void remove() {
             //如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null) {
                 m.remove(this);
             }
         }

    Summary

    Inside each thread there is a member variable named threadLocals, the type of this variable is Hash Map, where key is the this reference of the ThreadLocal variable we defined, and value is the value we set using the set method. The local variables of each thread are stored in the thread's own memory variable threadLocals. If the current thread never dies, these local variables will always exist, so it may cause memory overflow. Therefore, remember to call the remove method of ThreadLocal to delete after use. Local variables in threadLocals corresponding to the thread.

    ThreadLocal memory leak

    Why does a memory leak occur?

    ThreadLocalMap uses the weak reference of ThreadLocal as the key. If a ThreadLocal does not have an external strong reference to refer to it, then the ThreadLocal will inevitably be recycled during the system GC.

    In this way, the ThreadLocalMap will If an Entry with a null key appears

    , there is no way to access the value of these Entry with a null key. If the current thread does not end for a long time, there will always be a strong line for the value of these Entry with a null key. Reference chain: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value can never be recycled, causing memory leaks. In fact, this situation has been taken into consideration in the design of ThreadLocalMap, and some protective measures have been added: all keys in the thread ThreadLocalMap that are null will be cleared during get(), set(), and remove() of ThreadLocal. value. However, these passive preventive measures cannot guarantee that there will be no memory leaks:

      Using static ThreadLocal extends the life cycle of ThreadLocal, which may lead to memory leaks
    • Allocation uses ThreadLocal and no longer calls the get(), set(), remove() methods, which will lead to memory leaks
    • Why use weak references?

    Since we all know that using weak references will cause ThreadLocalMap memory leaks, why do officials still use weak references instead of strong references? This starts with the difference between using weak references and strong references:

    • If you use strong references: We know that the life cycle of ThreadLocalMap is basically the same as the life cycle of Thread. If the current thread is not terminated, then ThreadLocalMap will never be recycled by GC, and ThreadLocalMap holds the right to ThreadLocal. Strong reference, then ThreadLocal will not be recycled. When the thread life cycle is long, if it is not deleted manually, it will cause kv accumulation, resulting in OOM

    • If you use weak references: weak The object in the reference has a short declaration period, because during the system GC, as long as a weak reference is found, the object will be recycled regardless of whether the heap space is sufficient. When the strong reference of ThreadLocal is recycled, the weak reference held by ThreadLocalMap will also be recycled. If kv is not deleted manually, it will cause value accumulation and OOM

    From the comparison, we can see that using weak references can at least ensure that OOM will not be caused by the accumulation of map keys, and the corresponding value can be cleared on the next call through the remove, get, and set methods. It can be seen that the root cause of memory leaks is not weak references, but the life cycle of ThreadLocalMap is as long as Thread, causing accumulation.

    Solution

    Since the root of the problem is the accumulation of value causing OOM, Then we take the right medicine and call the remove() method every time we use ThreadLocal to clean it up.

    The above is the detailed content of How to use Java ThreadLocal class. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete