In-depth analysis of Java concurrency tool ThreadLocal

What is ThreadLocal

ThreadLocal is a tool class in Java that provides thread local variables. It means that it can create a variable copy for each thread to use, and each thread is isolated when accessing the variable copy without interfering with each other.

The main functions of ThreadLocal are:

  1. Thread isolation: Each thread has its own copy of variables and does not affect each other.
  2. Avoid passing parameters: pass data through ThreadLocal, no need to pass parameters between methods.
    Let's look at an example:
public class ThreadLocalExample {
    
    
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void add() {
    
    
        threadLocal.set(threadLocal.get() + 1);
    }

    public static Integer get() {
    
    
        return threadLocal.get();
    }

    public static void main(String[] args) {
    
    
        Thread thread1 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 3; i++) {
    
    
                    add();
                    System.out.println(get());
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 3; i++) {
    
    
                    add();
                    System.out.println(get());
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

Output results:
1
2
3
1
2
3
It can be seen that each thread has an independent copy of the threadLocal variable, and each accumulates and prints its own threadLocal value without affecting each other. This achieves the effect of thread isolation.
If you don't use ThreadLocal, use shared variables, like this:

public class NoThreadLocalExample {
    
    
    private static int num;

    public static void add() {
    
    
        num++;
    }

    public static int get() {
    
    
        return num; 
    }

    public static void main(String[] args) {
    
    
        // ...
    }
}

The output will be out of order, because two threads will compete to modify and read the shared variable num:
1
3
2
5
4
This is the problem that multiple threads will affect each other when ThreadLocal is not used.
There are three main points to note about ThreadLocal:

  1. Risk of memory leaks: ThreadLocal uses weak references to store data. If there are no external strong references, it may cause memory leaks after the thread dies. So after using ThreadLocal, call the remove() method to clear the data.
  2. Thread safety: ThreadLocal itself is thread safe, but the data objects stored in ThreadLocal are not necessarily thread safe. If multiple threads operate on the same data object, thread synchronization still needs to be considered.
  3. Inheritance: The child thread cannot obtain the data set by the parent thread to ThreadLocal. Each thread has its own copy of the variable.
    Summary:
    ThreadLocal provides an independent variable copy for each thread, achieving thread isolation and avoiding the effect of passing parameters. But there is also a risk of memory leaks, and the remove() method needs to be called to clear the data after use. And ThreadLocal only provides a variable copy for a single thread, and the child thread cannot obtain the data of the parent thread.
    ThreadLocal is suitable for scenarios where variables are isolated between threads and passed in the context of the method call chain. Its appearance allows us to write code more simply and elegantly in a high-concurrency environment.

What is the internal implementation principle of ThreadLocal?

ThreadLocal uses the internal class ThreadLocalMap to store the copy variables of each thread. ThreadLocalMap uses thread references as keys, and variable copies are stored as values ​​in a hash table.
When the get() method of ThreadLocal is called, the current thread will be obtained first, and then the variable copy will be obtained from the ThreadLocalMap associated with this thread. The set() method is similar, it will set the value of the ThreadLocalMap of the current thread.
Look at the implementation source code of ThreadLocal:

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

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

public void set(T value) {
    
    
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); 
}   

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

We can see that:

  1. Get the current thread through Thread.currentThread()
  2. Get the ThreadLocalMap object of the thread through the getMap() method
  3. If a ThreadLocalMap exists, get or set the value of a thread-local variable from it
  4. If ThreadLocalMap does not exist, create a ThreadLocalMap object and associate it with the thread before setting the value.
    Therefore, each thread maintains a ThreadLocalMap reference, which stores a copy of the variable with ThreadLocal as the key, and realizes the variable isolation of each thread.
    And ThreadLocal itself is only used as a key to identify variables, and does not hold specific values, which is also the basis for its implementation of thread isolation.

ThreadLocal is an important mechanism for implementing thread-local variables in Java. It creates a copy of the variable for each thread, so that each thread can independently change its own copy without affecting the corresponding copies of other threads.
The implementation principle of ThreadLocal is that it uses the internal class ThreadLocalMap to maintain a map of variable copies for each thread. ThreadLocalMap uses thread references as keys, and variable copies are stored as values ​​in a hash table.
When accessing or setting a variable copy through ThreadLocal, it will first obtain the reference of the current thread, and then obtain or set the corresponding value in the associated ThreadLocalMap through this thread. In this way, an independent variable copy is created for each thread, achieving the effect of thread isolation.
The main API of ThreadLocal has only three methods: get(), set() and remove(). It is simple and powerful, and a thorough understanding will bring a lot of convenience to our multi-threaded programming.
The application scenarios of ThreadLocal mainly include: database connection, transaction management, user context transfer, avoiding parameter transfer, etc. It is suitable for scenarios where variables are isolated between threads and need to be passed in the method call chain.
But the use of ThreadLocal also needs to pay attention to three points:

  1. Memory leak: Because ThreadLocal uses weak references, if there is no copy of the external strong reference variable, it may cause a memory leak. So after using it, call the remove() method to delete the reference.
  2. Thread safety: Objects stored in ThreadLocal must be thread safe, otherwise problems will occur in a multi-threaded environment.
  3. Overuse: Misuse of ThreadLocal can make code difficult to understand and maintain.

Use ThreadLocal to implement transaction management:

  1. Define ThreadLocal variable to store Connection:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
  1. Get the Connection from the data source when starting the transaction, and store it in ThreadLocal:
public void beginTransaction() throws SQLException {
    
    
    Connection conn = DataSourceUtils.getConnection();
    connectionHolder.set(conn);
    conn.setAutoCommit(false);
}
  1. Use the get() method in the transaction to get the Connection from ThreadLocal:
public void doSomeOperation() {
    
    
    Connection conn = connectionHolder.get();
    // 使用 conn 执行 SQL 操作
} 
  1. When committing or rolling back a transaction, get the Connection from ThreadLocal and commit or rollback:
public void commitTransaction() throws SQLException {
    
    
    Connection conn = connectionHolder.get();
    conn.commit();
    conn.close();
    connectionHolder.remove();
}

public void rollbackTransaction() throws SQLException {
    
    
    Connection conn = connectionHolder.get();
    conn.rollback();
    conn.close();
    connectionHolder.remove(); 
}
  1. Call remove() after the transaction to remove the Connection from the ThreadLocal.
    In this way, accessing Connection through ThreadLocal can realize the binding of transaction and thread, and ensure that each thread has its own independent Connection for transaction management.
    This is the idea of ​​using ThreadLocal to implement simple transaction management. A more robust transaction management framework will involve more content, but ThreadLocal is still the key to thread isolation.

Guess you like

Origin blog.csdn.net/pengjun_ge/article/details/131347197