Talk about the understanding of ThreadLocal

 In the multi-threaded module of java, ThreadLocal is a knowledge point that is often asked. There are many ways to ask questions. It may be a step-by-step or it may be just like my topic. Therefore, only a thorough understanding, no matter how you ask, all Can do well.

 This article mainly analyzes and understands from the following perspectives

 1. What is ThreadLocal

 2. How to use ThreadLocal

 3. ThreadLocal source code analysis

 4. ThreadLocal memory leak problem


What is ThreadLocal?

 From the name, we can see that ThreadLocal is called a thread variable, which means that the variable filled in ThreadLocal belongs to the current thread, and the variable is isolated from other threads. ThreadLocal creates a copy of the variable in each thread, so each thread can access its own internal copy variable.

 It is very easy to understand from the literal meaning, but from the point of view of actual use, it is not so easy. As a frequently asked point in an interview, the usage scenarios are also quite rich:

1. When passing objects across layers, using ThreadLocal can avoid multiple passes and break the constraints between layers.
2. Data isolation between threads
3. Perform transaction operations to store thread transaction information.
4. Database connection, Session session management.

How to use ThreadLocal?

Since the role of ThreadLocal is to create a copy for each thread, we use an example to verify:

    public static void main(String[] args) {
    
    

        //新建一个threadLocal
        ThreadLocal<String>local = new ThreadLocal<>();
        //新建一个随机数
        Random random = new Random();
        //利用java8的stream新建5个线程
        IntStream.range(0,5).forEach(a -> new Thread(() -> {
    
    
            //为每一个线程设置相应的local值
            local.set(a+ "  " + random.nextInt(10) );
            System.out.println("线程和local值分别是: "+ local.get());
            try{
    
    
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }).start() );
    }
//        线程和local值分别是: 0  0
//        线程和local值分别是: 3  0
//        线程和local值分别是: 4  2
//        线程和local值分别是: 2  6
//        线程和local值分别是: 1  3

 From the results, we can see that each thread has its own local value. We set a sleep time so that another thread can read the current local value in time.

 This is the basic use of TheadLocal, is it very simple? So why is it used more when connecting to the database?

When we use the database, we first establish a database connection, and then close it when we run out. This has a very serious problem. If a client frequently uses the database, then we need to establish multiple connections and close it. Our server may be overwhelmed, what should we do? If there are 10,000 clients, the server pressure is even greater.

ThreadLocal is best at this time, because ThreadLocal creates a copy of the connection in each thread, and it can be used anywhere within the thread, and the threads do not affect each other, so there is no thread safety problem, and it will not Seriously affect program execution performance. Is it very useful?

The above is mainly to explain a basic case, and then analyze why ThreadLocal is used when connecting to the database. Let's analyze the working principle of ThreadLocal from the perspective of source code.

ThreadLocal source code analysis

In the first example, only two methods are given, namely get and set methods. In fact, there are a few more that need our attention.

  1. set method

     /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
          
          
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    From the set method, we can see that the current thread t is first obtained, and then getMap is called to obtain the ThreadLocalMap. If the map exists, the current thread object t is used as the key, and the object to be stored is stored in the map as the value. If the Map does not exist, initialize one.

    OK, at this point, I believe you will have some doubts, what is ThreadLocalMap, and how the getMap method is implemented. With these questions, continue to look down. First look at ThreadLocalMap.

        static class ThreadLocalMap {
          
          
    
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
          
          
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
          
          
                super(k);
                value = v;
            }
        }
    

    We can see that ThreadLocalMap is actually a static internal class of ThreadLocal, which defines an Entry to save data, and it is also an inherited weak reference. Use ThreadLocal as the key inside Entry, and use the value we set as the value.

    There is also a getMap

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

    Call current thread t and return the member variable threadLocals in current thread t. And threadLocals is actually ThreadLocalMap.

  2. get method

        /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }
    

    Through the introduction of ThreadLocal above, I believe you have a good understanding of this method. First get the current thread, and then call the getMap method to get a ThreadLocalMap. If the map is not null, then use the current thread as the entry key of the ThreadLocalMap, and then the value As the corresponding value, if not, set an initial value.

    How to set an initial value?

        /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
          
          
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
  3. The remove method
    can be removed from our map.

        /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
          
          
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    

(1) Each Thread maintains a reference to ThreadLocalMap

(2) ThreadLocalMap is an internal class of ThreadLocal, using Entry to store

(3) The copy created by ThreadLocal is stored in its own threadLocals, which is its own ThreadLocalMap.

(4) The key value of ThreadLocalMap is a ThreadLocal object, and there can be multiple threadLocal variables, so they are stored in the map

(5) Before getting get, you must first set, otherwise a null pointer exception will be reported. Of course, one can also be initialized, but the initialValue() method must be rewritten.

(6) ThreadLocal itself does not store the value, it just serves as a key to let the thread get the value from the ThreadLocalMap.

Several other points to note in ThreadLocal

Insert picture description here
Any article that introduces ThreadLocal will help you understand one point, that is, the memory leak problem. Let's first look at the picture below.

The above picture reveals the relationship between ThreadLocal and Thread and ThreadLocalMap in detail.

1. There is a map in Thread, which is ThreadLocalMap

2. The key of ThreadLocalMap is ThreadLocal, and the value is set by ourselves.

3. ThreadLocal is a weak reference. When it is null, it will be treated as garbage.

4. The point is here. Suddenly our ThreadLocal is null, which means it will be recycled by the garbage collector. But at this time, our ThreadLocalMap has the same life cycle as Thread. It will not be recycled. At this time, there is a phenomenon. That is, the key of ThreadLocalMap is gone, but the value is still there, which causes a memory leak.

Solution: After using ThreadLocal, perform the remove operation to avoid memory overflow.

Guess you like

Origin blog.csdn.net/woaichihanbao/article/details/107907791