Java multithreaded ThreadLocal

ThreadLocal

ThreadLocal is provided by the JDK package, which provides thread-local variables, that is, if you create a ThreadLocal variable, then every thread that accesses this variable will have a local copy of this variable . When multiple threads manipulate this variable, they actually manipulate the variables in their own local memory, thus avoiding thread safety issues. After creating a ThreadLocal variable, each thread will copy a variable to its own local memory.

The role of ThreadLocal (what's the use)?

Concurrency problems are particularly prone to occur when multiple threads access the same shared variable, especially when multiple threads need to write to a shared variable. In order to ensure thread safety, general users need to perform proper synchronization when accessing shared variables. The synchronization measure is generally to add locks. This requires users to have a certain understanding of locks, which obviously increases the burden on users. So is there a way to do that, when a variable is created, when each thread accesses it, it is the variable of its own thread? In fact, ThreadLocal can do this, although ThreadLocal is not meant to solve this The problem arises.

The principle of ThreadLocal

First look at the class diagram structure of ThreadLocal related classes:
Insert picture description herefrom the figure, there is a threadLocals and an inheritableThreadLocals in the Thread class , which are variables of type ThreadLocalMap, and ThreadLocalMap is a customized Hashmap . By default, these two variables in each thread are null, and they will be created only when the current thread calls the ThreadLocal set or get method 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 a specific thread memory space. ThreadLocal is a tool shell. It puts the value value into the threadLocals of the calling thread through the set method and stores it. When the calling thread calls its get method, it is used from the threadLocals variable of the current thread . If the calling thread never terminates, then this local variable will always be stored in the threadLocals variable of the calling thread, so when you do not need to use the local variable, you can call the remove method of the ThreadLocal variable to delete the local variable from the threadLocals of the current thread. 另外,Thread里面的threadLocals为何被设计为map结构?很明显是因为每个线程可以关联多个ThreadLocal变量.

Insert picture description here

The following briefly analyzes the implementation logic of ThreadLocal's set, get and remove methods.

set method

Code (1) First get the calling thread, and then use the current thread as a parameter to call the getMap(t) method. You can see that the function of getMap(t) is to get the thread's own variable threadLocals, and the threadlocal variable is bound to the thread member variable on.
If the return value of getMap(t) is not empty, set the value to threadLocals, that is, put the current variable value into the memory variable threadLocals of the current thread. ThreadLocals is a HashMap structure, where key is the object reference of the current ThreadLocal instance, and value is the value passed through the set method. If getMap(t) returns a null value, it means that the set method is called for the first time, and the threadLocals variable of the current thread is created at this time.
// set()方法
public void set(T value) {
    
    
	// (1)获取当前线程
    Thread t = Thread.currentThread();
    // (2)将当前线程作为key,去查找对应的线程变量,找到则设置
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    	// (3)第一次调用就创建当前线程对应的HashMap
        createMap(t, value);
}

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

// createMap()方法
void createMap(Thread t, T firstValue) {
    
    
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get() method

public T get() {
    
    
	// (4)获取当前线程
   Thread t = Thread.currentThread();
   //(5)获取当前线程的threadLocals变量
   ThreadLocalMap map = getMap(t);
   //(6)如果threadLocals不为null,则返回对应本地变量的值
   if (map != null) {
    
    
       ThreadLocalMap.Entry e = map.getEntry(this);
       if (e != null) {
    
    
           @SuppressWarnings("unchecked")
           T result = (T)e.value;
           return result;
       }
   }
   // ( 7) threadLocals为空则初始化当前线程的threadLocals成员变量
   return setInitialValue();
}

// setInitialValue()
private T setInitialValue() {
    
    
	// (8)初始化为null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // (9)如果当前线程的threadLocals变量不为空
    if (map != null)
        map.set(this, value);
    else
    	//(10)如果当前线程的threadLocals变量为空
        createMap(t, value);
    return value;
}

// initialValue()
protected T initialValue() {
    
    
   return null;
}

remove() method

public void remove() {
    
    
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

InheritableThreadLocal

ThreadLocal does not support inheritance, that is, after the same ThreadLocal variable is set in the parent thread, it cannot be obtained in the child thread. In order to solve this problem, InheritableThreadLocal came into being. InheritableThreadLocal inherits from ThreadLocal, which provides a feature that allows child threads to access local variables set in the parent thread. Let's take a look at the code of InheritableThreadLocal.

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    
	//(1)
	protected T childValue(T parentValue) {
    
    
        return parentValue;
    }
	//(2)
	ThreadLocalMap getMap(Thread t) {
    
    
       return t.inheritableThreadLocals;
    }
	//(3)
	void createMap(Thread t, T firstValue) {
    
    
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

As can be seen from the above code, InheritableThreadLocal inherits ThreadLocal and rewrites three methods. From the code (3), we can see that InheritableThreadLocal overwrites the createMap method. Now when the set method is called for the first time, it creates an instance of the inheritableThreadLocals variable of the current thread instead of threadLocals. The code (2) shows that when the get method is called to get the map variable inside the current thread, it gets inheritableThreadLocals instead of threadLocals.

ThreadLocal memory leak problem

In fact, the key used in ThreadLocalMap is a weak reference of ThreadLocal. The feature of weak reference is that if there is only a weak reference for this object, it will inevitably be cleaned up during the next garbage collection.

Therefore, if the ThreadLocal is not strongly referenced externally, it will be cleaned up during garbage collection, so that the key that uses this ThreadLocal in the ThreadLocalMap will also be cleaned up. However, value is a strong reference and will not be cleaned up. In this way, a value with a null key will appear.

This situation has been considered in the implementation of ThreadLocalMap. When the set(), get(), and remove() methods are called, the records whose key is null will be cleaned up. If there will be a memory leak, it is only when the remove() method is not manually called after the record with the key is null, and the get(), set(), and remove() methods are no longer called afterwards.

ThreadLocalRandom类

We know that Random class helps us to generate random numbers. For example, to generate multiple integers of int type, we will use the nextInt() method. In a single thread, there is no problem. What if it is multi-threaded? Let's take a look at the source code of nextInt() first:

protected int next(int bits) {
    
    
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
    
    
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

It can be seen that the generation of a new random number requires two steps:·

  • First, generate new seeds based on the old seeds.
  • Then calculate a new random number based on the new seed.

Due to oldseed = seed.get();the atomicity problem, in the case of high concurrency, the old seeds obtained by multiple threads may be the same, and then the new seeds generated are the same, and the final result is that the generated random numbers are also the same.

In order to make up for the shortcomings of Random in the case of multi-threaded high concurrency, the ThreadLocalRandom class was added under the JUC package.

First look at the class diagram structure of ThreadLocalRandom:

![Insert picture description here](https://img-blog.csdnimg.cn/20210125192430649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNz80NQdlaMFF,size_FF=70FF,size_color,

It can be seen from the figure that the ThreadLocalRandom class inherits the Random class and overrides the nextInt method. The atomic seed variable inherited from the Random class is not used in the ThreadLocalRandom class. There is no specific seed stored in ThreadLocalRandom. The specific seed is stored in the threadLocalRandomSeed variable of the specific calling thread. ThreadLocalRandom is similar to the ThreadLocal class, which is a tool class. When a thread calls the current method of ThreadLocalRandom, ThreadLocalRandom is responsible for initializing the threadLocalRandomSeed variable of the calling thread, which is the initialization seed.

When the nextInt method of ThreadLocalRandom is called, the threadLocalRandomSeed variable of the current thread is actually obtained as the current seed to calculate the new seed, and then the new seed is updated to the threadLocalRandomSeed variable of the current thread, and then the random number is calculated according to the new seed and using a specific algorithm . It should be noted here that the threadLocalRandomSeed variable is an ordinary long variable in the Thread class,
it is not an atomic variable. In fact, the reason is very simple, because this variable is thread-level, so there is no need to use atomic variables, if you still do not understand the principle of ThreadLocal.

Guess you like

Origin blog.csdn.net/weixin_44533129/article/details/113111127