Let’s talk about your understanding of ThreadLocal

preface

When I was asked about ThreadLocal during the interview, I didn't answer well (grandma's, now I feel that I can be asked anything), so I decided to solve the high-frequency problems encountered in these interviews first, and speak out these hard bones thoroughly. I feel that at least I can't always travel in one round.

ThreadLocal introduction

ThreadLocal is jdk1 2 provides a class for storing thread local variables from the beginning. The variables in ThreadLocal exist independently in each thread. When multiple threads access the variables in ThreadLocal, they actually access the variables in the memory of their current thread, so as to ensure the thread safety of the variables.

We generally use ThreadLocal to solve the problem of variable competition in threads. In fact, to solve such problems, we usually think of using synchronized to lock.

For example, when solving the thread safety of simpledateformat. Simpledateformat is non thread safe. Both the format () method and the parse () method have a calendar class object inside it. The format method is to set the time. In the parse () method, the clear () method of calendar is called first, and then the set () method of calendar is called (assignment). If a thread has just called set () for assignment, At this time, another thread directly calls the clear () method, and the execution result of the parse () method will be problematic. Solution 1: the method of using simpledateformat plus synchronized ensures thread safety, but reduces efficiency. Only one thread can use the method of formatting time at the same time.

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static synchronized String formatDate(Date date){
    return simpleDateFormat.format(date);
}

Solution 2: put the object of simpledateformat into ThreadLocal, so that each thread has a copy of its own format object. It does not interfere with each other, so as to ensure thread safety.

private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static String formatDate(Date date){
   return simpleDateFormatThreadLocal.get().format(date);
}

Principle of ThreadLocal

Let's take a look at how ThreadLocal is used.

ThreadLocal<Integer> threadLocal99 = new ThreadLocal<Integer>();
threadLocal99.set(3);
int num = threadLocal99.get();
System.out.println("数字:"+num);
threadLocal99.remove();
System.out.println("数字Empty:"+threadLocal99.get());

Operation results:

数字:3
数字Empty:null

It's easy to use. It's mainly to put variables into ThreadLocal, which can be obtained during thread execution. When execution is completed, it can be removed. As long as remove() is not called, the current thread can get variable data during execution. Because it is placed in the currently executing thread, the variable value in ThreadLocal can only be used by the current thread, so as to ensure thread safety (in fact, the child threads of the current thread can also be obtained).

Take a look at the source code of ThreadLocal's set () method

public void set(T value) {
   // 获取当前线程
   Thread t = Thread.currentThread();
   // 获取ThreadLocalMap
   ThreadLocal.ThreadLocalMap map = getMap(t);
   // ThreadLocalMap 对象是否为空,不为空则直接将数据放入到ThreadLocalMap中
   if (map != null)
       map.set(this,value);
   else
       createMap(t,value); // ThreadLocalMap对象为空,则先创建对象,再赋值。
}

We can see that the variables are stored in the threadlocalmap variable. So how did threadlocalmap come from?

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;
    ... ...
}

Through the above source code, we find that the threadlocalmap variable is a variable in the current execution thread, so the data stored in ThreadLocal is actually placed in a variable in the current execution thread. That is, it is stored in the current thread object. In other threads, there is another thread object. The data in other thread objects cannot be obtained, so the data is naturally separated.

So how does threadlocalmap store data? Threadlocalmap is an internal class in ThreadLocal class. Although the name of the class carries map, it does not implement the map interface, but its structure is similar to that of map.

/**
 * Set the value associated with key.
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key,Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones,in which case,a fast
    // path would fail more often than not.

    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和传过来的key相等,那么就会覆盖当前位置的数据
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果当前位置为空,则初始化一个Entry对象,放到当前位置。
        if (k == null) {
            replaceStaleEntry(key,value,i);
            return;
        }
    }
    // 如果当前位置不为空,并且当前位置的key也不等于要赋值的key ,那么将去找下一个空位置,直接将数据放到下一个空位置处。
    tab[i] = new Entry(key,value);
    int sz = ++size;
    if (!cleanSomeSlots(i,sz) && sz >= threshold)
        rehash();
}

We can see from the set () method that the processing logic has four steps.

Similar logic is used in get. First, get the position in the entry array through the hashcode of the passed in ThreadLocal, and then compare the key of the entry in the current position with the passed in ThreadLocal. If it is equal, directly return the data. If it is not equal, judge whether it is equal to the key of the next value in the group...

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);
}
/**
 * Version of getEntry method for use when key is not found in
 * its direct hash slot.
 *
 * @param  key the thread local object
 * @param  i the table index for key's hash code
 * @param  e the entry at table[i]
 * @return the entry associated with key,or null if no such
 */
private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e) {
    Entry[] tab = table;
    int len = tab.length;

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

We have always said that ThreadLocal is the data stored in a single thread. Each thread has its own data, but the real object data in the actual ThreadLocal is actually stored in the heap, and the thread only stores the reference of the object. And when we use it, we usually need to share the variables in ThreadLocal in the context of the method executed by the previous thread. For example, my main thread executes code in a method, but when there is a piece of code in this method, a new thread is created, and the variables in ThreadLocal defined in my executing method are also used in this thread. At this time, it is necessary to call the data of the external thread from the new thread, which needs to be shared among threads. ThreadLocal also supports data sharing between child and parent threads. For example:

 ThreadLocal threadLocalMain = new InheritableThreadLocal();
 threadLocalMain.set("主线程变量");
 Thread t = new Thread() {
     @Override
     public void run() {
         super.run();
         System.out.println( "现在获取的变量是 =" + threadLocalMain.get());
     }
 };
 t.start();

Operation results:

现在获取的变量是 =主线程变量

The above code can realize the sharing of data between child and parent threads, focusing on the sharing using inheritablethreadlocal. So how does it realize data sharing? In the init () method of thread class, there is a piece of code:

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

This code means that when creating a thread, if the inheritthreadlocals variable of the current thread and the inheritthreadlocals variable of the parent thread are not empty, the data in the inheritthreadlocals variable of the parent thread will be assigned to the inheritthreadlocals variable of the current thread.

ThreadLocal memory leak

As mentioned above, the entry object in threadlocalmap in ThreadLocal inherits from the WeakReference class, indicating that the key of the entry is a weak reference.

This weak reference is still the ThreadLocal object itself, so generally, after the thread execution is completed, the ThreadLocal object will become null, and the null weak reference object will be cleared at the next GC, so the memory space of the entry key will be released, but the entry value still occupies the memory, If threads are reused (such as threads in the thread pool), there will be more and more value values, which will eventually lead to memory leakage.

The way to prevent memory leakage is to execute the following remove () method every time ThreadLocal is used, so as to free the space of key and value.

Since it is prone to memory leakage, why set it to weak reference? Under normal circumstances, strong references should be strong references, but strong references will not be recycled as long as the reference relationship is still there. Therefore, if the thread is reused, the key and value in the entry will not be recycled, resulting in memory leakage of both key and value.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>