Java Multithreading | Detailed ThreadLocal Implementation Principle

1. Introduction of ThreadLocal:

Under normal circumstances, the variables we create can be accessed and modified by any thread. If we want the thread to have its own private local variables , then we can use ThreadLocalclasses to achieve this idea.

ThreadLocalThe main solution of the class is to allow each thread to bind its own value . The ThreadLocal class can be compared to a box for storing data, and the private data of each thread can be stored in the box.

Let's take a look at a sample code to understand its use:

import java.text.SimpleDateFormat;
import java.util.Random;

/**
 * ThreadLocal示例类
 */
public class ThreadLocalExample {

    //SimpleDateFormat不是线程安全的,所以要为每个线程都创建一个本地副本
    private static final ThreadLocal<SimpleDateFormat> formarter =
            ThreadLocal.withInitial(()->new SimpleDateFormat("yyyyMMdd HHmm"));

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

        ThreadLocalExample example = new ThreadLocalExample();
        for (int i = 0;i < 10; ++i){
            //创建线程
            Thread t = new Thread(()->{
                System.out.println("Thread Name="+Thread.currentThread().getName()+" deafult Formatter=" +
                        formarter.get().toPattern());
                try {
                    Thread.sleep(new Random().nextInt(1000));
                }catch (Exception e){
                    e.printStackTrace();
                }

                formarter.set(new SimpleDateFormat());

                System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter=" +
                        formarter.get().toPattern());
            },""+i);

            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }

    }

}


From our results, it can be seen, thread 0 uses set the method to modify the value of the variable has changed, but for no other threads in the call set before the method, get take the values are beginning to initialize the main thread of the default values.

Second, the realization principle of ThreadLocal

Let's first look at a very important static inner class in the ThreadLocal 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 static final int INITIAL_CAPACITY = 16;
        private Entry[] table;
        private int size = 0;
        .......

This ThreadLocalMapwe can regard it as a similar Mapdata structure, its key is an ThreadLocalobject, and its value is an Objectobject.

In fact Thread, there are two ThreadLocalMapobjects in the class , let's take a look:

public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...

By default, these two variables are null, and they are created only when the current thread calls the set or get method of the ThreadLocal class.

In fact, the ThreadLocalstored private variables of each thread are stored in the threadLocalsobject of the current thread object . When ThreadLocalthe setmethod is called , the Thread.currentThread()current thread t is obtained through first , and then the getMap(t)method is used to obtain the corresponding to the current thread ThreadLocalMap, and then Operations on this map:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们来看看上面那个通过线程来获取ThreadLocalMapgetMap()方法的实现:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

这个方法就是直接获取了线程对象里面的threadLocals,这个在我们刚刚看Thread类的时候已经说到了这个对象了。

因此我们可以得出一个结论就是:最终的变量是放在了当前线程的ThreadLocalMap
中,并不是存在ThreadLocal上,ThreadLocal可以理解为只是ThreadLocalMap的封装,传递了变量值。ThrealLocal类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

比如我们在同⼀个线程中声明了两个ThreadLocal对象的话,会使⽤Thread内部都是使⽤仅有那个ThreadLocalMap存放数据的,ThreadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调⽤set⽅法设置的值。


三、关于ThreadLocal的内存泄露的问题

我们来看看ThreadLocalMap中的键值对对象Entry

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样⼀来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产⽣内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录。使用完ThreadLocal方法后最好手动调用remove()方法。

Guess you like

Origin blog.csdn.net/qq_14810195/article/details/107835086