ThreadLocal Analysis of Java Multithreading

1. Understanding ThreadLocal

ThreadLocal, also known as thread local variables or thread local storage. ThreadLocal creates a copy of the variable in each thread, so each thread can access its own internal copy variable. Let's start with an example:
class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}
Assuming there is a database connection management class, there is no problem with this code when it is used in a single thread, but what if it is used in a multi-thread? Obviously, there will be thread security problems in multi-threading: first, there is no synchronization between the two methods, and it is possible to create a connection multiple times in the open Connection method; second, because the connection is a shared variable, it is necessary to use synchronization to ensure thread security in the place where the connection is called, because it is likely that a thread uses connection for database. Operations, while another thread calls closeConnection to close the connection.
So for thread security reasons, it is necessary to synchronize the two methods of this code, and synchronize them where connect is called.
This will greatly affect the execution efficiency of the program, because when one thread uses connect for database operations, the other threads have to wait.
So let's take a closer look at this question. Does this place need to share connect variables? In fact, it is not necessary. If there is a connection variable in each thread, the access to the connection variable between threads is virtually independent, that is, one thread does not care whether other threads have modified the connection.
At this point, it may be considered that, since there is no need to share this variable between threads, it can be handled directly. A database connection is created only when it is specifically used in each method that needs to use a database connection, and then the connection is released after the method call is completed, such as the following:
class ConnectionManager {
     
    private  Connection connect = null;
     
    public Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}
 
 
class Dao{
    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection();
         
        //Use connection to operate
         
        connectionManager.closeConnection();
    }
}

There's really no problem with this, because every time a connection is created within a method, there's naturally no thread security problem between threads. But this can have a fatal impact: causing server pressure is very high, and seriously affecting the performance of program execution. Because of the frequent opening and closing of database connections in the method, it not only seriously affects the efficiency of program execution, but also causes tremendous pressure on the server.
In this case, using ThreadLocal is perfect, because ThreadLocal creates a copy of the variable in each thread, that is, there will be a variable inside each thread, and there will be a variable inside the thread, and it can be used anywhere inside the thread, and threads will not affect each other, so there will be no thread safety problems, nor will there be any thread safety problems. Seriously affecting program execution performance
However, it should be noted that although ThreadLocal can solve the problems mentioned above, since replicas are created in each thread, consideration should be given to its resource consumption, such as memory usage will not be large without ThreadLocal.

2. Deep understanding of ThreadLocal classes

Now let's see how ThreadLocal is implemented.
Let's first look at some of the methods provided by the ThreadLocal class
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
The get() method is used to get the copy variables saved by ThreadLocal in the current thread, set() is used to set the copy of the variables in the current thread, remove() is used to remove the copy of the variables in the current thread. Initial Value () is a protected method, which is generally used to rewrite in use. It is a delayed loading method, which will be explained in detail below.
Let's first look at how the ThreadLocal class creates a copy of a variable for each thread.
First look at the implementation of get method:
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();
}

The first sentence is to get the current thread, and then get a map through the getMap(t) method. The type of map is ThreadLocalMap. Then you get the < key, value > key-value pair below. Note that the key-value pair passed here is this, not the current thread t.

If successful, the value value value is returned
If the map is empty, the setInitialValue method is called to return value.
Let's specifically analyze what the getMap method does:
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
In fact, it returns to threadLocals, a member variable in the current thread t. Let's continue to see what the variable threadLocals is.
ThreadLocal.ThreadLocalMap threadLocals = null;

In fact, it's a ThreadLocalMap, which is an internal class of the ThreadLocal class. Let's continue to look at the implementation of the 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;
        }
    }
}
You can see that Entry of ThreadLocalMap inherits WeakReference and uses ThreadLocal as the key value.
Continue to look at the specific implementation of the setInitialValue method:
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;
}
It's easy to understand that if the map is not empty, set the key value to be empty, create a Map, and see the implementation of a createMap:
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
So far, it's almost clear how ThreadLocal creates copies of variables for each thread:
First, there is a member variable threadLocals of ThreadLocal.ThreadLocalMap type inside each thread Thread. This threadLocals is used to store a copy of the actual variable. The key value is the current ThreadLocal variable, and the value is the copy of the variable (that is, the variable of type T).
Initially, ThreadLocals are empty in Thread. When the get() method or set() method is called through the ThreadLocal variable, threadLocals in the Thread class are initialized, and the current ThreadLocal variable is the key value, and the copy variable to be saved by ThreadLocal is the value, which is saved to threadLocals.
Then in the current thread, if you want to use replica variables, you can find them in threadLocals by get method.
Following is an example of how ThreadLocal can create a copy of a variable in each thread:
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

After running, you will find that in the main thread and thread1 thread, the copy value saved by longLocal is different from that saved by stringLocal. The last time the copy value is printed again in the main thread is to prove that the copy value in the main thread is really different from that in the thread1 thread.

To sum up:
  1. In fact, copies created through ThreadLocal are stored in each thread's own threadLocals
  2. Why is the key value of the type ThreadLocalMap of threadLocals ThreadLocal object because there are multiple threadLocal variables in each thread, just like longLocal and stringLocal in the code above?
  3. Before get ting, you must set first, otherwise null pointer exceptions will be reported.
If you want normal access without calling set before get, you must override the initialValue() method
In the code analysis above, we found that if there is no set first, that is, if the corresponding storage is not found in the map, i will be returned by calling the setInitialValue() method, while in the setInitialValue method, one statement is T value = initialValue(), and by default, initialValue returns null.
Look at the following example:
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
 
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

In the main thread, if there is no set first and get directly, the runtime will report null pointer exception.

But if you change to the following code, you rewrite the initialValue method:
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
        protected Long initialValue() {
            return Thread.currentThread().getId();
        };
    };
    ThreadLocal<String> stringLocal = new ThreadLocal<String>(){
        protected String initialValue() {
            return Thread.currentThread().getName();
        };
    };
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
 
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}
You can call get directly without set ting first.

3.ThreadLocal application scenarios

The most common ThreadLocal usage scenarios are used to solve database connection, Session management, and so on.
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
  public Connection initialValue() {
    return DriverManager.getConnection(DB_URL);
  }
};
 
public static Connection getConnection() {
  return connectionHolder.get();
}
private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

4. Will ThreadLocal cause memory leaks?
Why do you think so? (ThreadLocalMap uses the weak reference of ThreadLocal as Key. If a ThreadLocal does not have an external strong reference to it, the object will be reclaimed when the system GC is running. In this way, the keys in the ThreadLocalMap are null and can not be accessed. If the current thread does not end late, there will always be a strong reference chain for the value of Entry whose keys are null. Never Recycle)
But it doesn't: sort out the flow of ThreadLocalMap's getEntry function:
  1. Entry is first obtained from the direct index location of ThreadLocal (location via ThreadLocal.threadLocalHashCode & (len-1)) and returned if e is not null and the key is the same.
  2. If e is null or key is inconsistent, the next location is queried. If the key of the next location is equal to the key of the current query, the corresponding Entry is returned. In addition, if the key value is null, the Entry of that location is erased, otherwise the next location is queried.
As can be seen from the above, Entry with null key will be erased in this process, so value in Entry will not have strong reference chain and will be recycled naturally. Set also has a kind of idea. But this idea can only be realized by calling set and get functions, so in many cases it is necessary to call remove functions manually and delete unnecessary TreadLocal manually.
JDK recommends that ThreadLocal variables be defined as private static, so that the lifetime of ThreadLocal is longer. Because there are always strong references to ThreadLocal, ThreadLocal will not be recycled, which ensures that the value of Entry can be accessed at any time according to weak references of ThreadLocal, and then remove d to prevent memory leaks.

 

Keywords: Database Session JDK

Added by nickk on Thu, 16 May 2019 22:35:21 +0300