Multithreading - ThreadLocal

A ThreadLocal definition

ThreadLocal is a thread local variable, which is a mechanism for saving thread private information.

Through ThreadLocal, each thread can be provided with a unique variable copy of the thread to ensure that the variables accessed between each thread do not affect each other. This variable only works in the life cycle of the thread and can be used anywhere in this thread to reduce the complexity of variable transfer between multiple functions in the same thread.

ThreadLocal is a way to solve concurrency problems in java multi-threaded programs. In addition to ThreadLocal, we can also ensure the thread safety of variables through the mechanism of locking (eg: synchronized  ), but locking will affect the execution efficiency of the program. At this time, ThradLocal can come in handy;

Two usage scenarios

1. Spring transaction isolation is implemented based on ThreadLocal and AOP, see TransactionSynchronizationManager for specific source code;

2. Spring uses ThreadLocal to ensure that database operations in a single thread use the same database connection;

3. Session management;

4. When the business involves multiple methods and multi-level calls, put the request parameters into ThreadLocal to prevent transitional parameter passing, such as transparently transmitting the user id from the controller to the dao layer, etc.;

5. Implement SimpleDateFormat thread safety based on ThreadLocal;

6、... ...

Three APIs

// 获取当前线程中的共享变量的副本
public T get() {}

// 设置当前线程中变量的副本值
public void set(T value) {}

// 移除当前线程中变量的副本,回收内存;
// 其实这个不是必须要调用的,因为当线程失效之后,这些内存会自动释放
// 未避免出现内存泄露问题,建议在线程结束前手动调用该方法清空数据
public void remove() {}

// get调用时的默认值,默认返回null
// protected方法;方便实际业务中根据实际需要重写方法
protected T initialValue() {}

Call example:

        ThreadLocal<String> tl = new ThreadLocal<>();
        
        tl.set("zhangsan");
        
        System.out.println(tl.get());
        
        tl.remove();

set()

Source code example:

    public void set(T value) {
        Thread t = Thread.currentThread();  // 获取当前线程
        ThreadLocal.ThreadLocalMap map = getMap(t);  // 获取当前线程 ThreadLocalMap 对象
        if (map != null)
            map.set(this, value);  // 不为空则set
        else
            createMap(t, value);  // 为空则初始化当前线程 ThreadLocalMap
    }

The implementation of the set() method is relatively simple, mainly focusing on the ThreadLocalMap object. The ThradLocalMap object is a threadLocals variable obtained from the current thread:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
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;

Each thread maintains its own threadLocals. When a thread creates a TreadLocal and saves a variable, the variable actually exists in the threadLocals variable of the current thread itself. Naturally, other threads cannot obtain the variables of the current thread, and data isolation is realized;

Let's take a look at ThreadLocalMap again:

static class 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;
            }
        }

        // 初始容量 默认16
        private static final int INITIAL_CAPACITY = 16;

        // 数组结构存储数据
        private Entry[] table;

        /** The number of entries in the table. */
        private int size = 0;

        /** The next size value at which to resize. */
        private int threshold; // Default to 0

        // resize门限 2/3
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

It can also be seen from the above source code that ThreadLocal does not store any value, it is only used as the key of ThreadLocalMap, so that the thread can get the corresponding value.

There is an Entry array in ThreadLocalMap. The reason why data is used is that there may be multiple different ThreadLocals in a thread to store different objects, so an array is used here to store;

The data structure of ThreadLocalMap is a bit like HashMap, but it does not implement the Map interface, and its Entry inherits the weak reference of WeakReference, and there is no next in HashMap, so there is no linked list. Therefore, when there is a hash conflict, ThreadLocalMap uses linear detection to resolve the hash conflict. See source code:

private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                // 如果当前位置不为空,且key即为当前threadlocal,则刷新value
                if (k == key) {
                    e.value = value;
                    return;
                }

                // 如果当前位置为空,则初始化一个Entry对象
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

When setting elements, ThreadlocalMap will first obtain the key, which is the threadLocalHashCode of the current ThreadLocal, and locate the element in the table according to the value of threadLocalHashCode:

    1. If the current location is empty, initialize an Entry object and place it at the current location;

    2. If the current location is not empty and the key is the current threadlocal, refresh the value;

    3. If it is currently empty or not, and the key is not the current threadlocal, continue to iterate the table to find the next empty position;

get()

Source code example:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
             return e;
        else
             return getEntryAfterMiss(key, i, e);
    }

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        // 根据key的hashcode获取在table中的下标值,循环查找元素对比key是否相等
        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
         }
         return null;
    }

Similar to set(), it also obtains the ThreadLocalMap of the current thread first, and then uses the threadLocalHashCode of threadlocal to locate the current threadlocal in the table in map.getEntry(), and then loops through the elements to compare keys and retrieve data; 

memory leak problem

Going back to the Entry object of ThreadLocalMap, it is mentioned above that the Entry object inherits the WeakReference weak reference, see the definition of weak reference:

弱引用(Weak Reference):弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,
不管当前内存空间足够与否,都会回收它的内存

This leads to a problem, that is, when the current threadlocal has no external references, if the GC is triggered at this time, the key of the Entry, that is, the threadlocal, will be recycled, and if the thread that created the current Theadlocal has been running, the value object of the Entry may not be recycled, and memory leaks may occur over time;

Therefore, in order to avoid the above memory overflow problem, when using threadlocal, remember to call the remove() method in ThreadLocal at the end of the thread code to clear the value, so that the value will be recycled when GC is triggered to avoid memory leaks;

Parent and child threads share ThreaLocal

The inheritableThreadLocals attribute is provided in java.lang.Thread to support obtaining parent thread thread variables

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

Code example:

    public static void main(String[] args) {
        ThreadLocal<String> tl = new InheritableThreadLocal<>();
        tl.set("zhangsan");

        Thread child = new Thread() {
            @Override
            public void run() {
                super.run();
                System.out.println(tl.get());
            }
        };

        child.start();
    }

See the init method of the Thread class for the implementation principle:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
 
        ... ...

        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        ... ...
    }

If the inheritableThreadLocals attribute of the parent thread is not empty, assign the inheritableThreadLocals attribute of the parent thread to the child thread to realize the shared threadlocal between the parent and child threads;

Guess you like

Origin blog.csdn.net/sxg0205/article/details/108383908