The use of ThreadLocal in Java interviews

What problem does ThreadLocal solve? What is the internal source code like?

Role: Implements passing objects in the context of threads, creating a copy for each thread.

Case:

public class ThreadLocalTest {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        new Thread(task).start();
        Thread.sleep(10);
        new Thread(task).start();
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            Long result = threadLocal.get();
            if(result == null){
                threadLocal.set(System.currentTimeMillis());
            }
            System.out.println(Thread.currentThread().getName()+"->"+threadLocal.get());
        }
    }
}

The output is different:

Thread-0->1607250402525

Thread-1->1607250402535

 

Why it is possible to keep a different copy for each thread

Analyze the source code:

Long result = threadLocal.get();

public T get() {
	//1. Get the current thread
        Thread t = Thread.currentThread();
	//2. Get the map corresponding to the current thread
        ThreadLocalMap map = getMap(t);
        if (map != null) {
	    //3. Use threadLocal as the key to get the entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
		//4. Get the value of the corresponding entry, which is a copy of the variable we stored in it
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

threadLocal.set(System.currentTimeMillis());

public void set(T value) {
	//1. Get the current thread
        Thread t = Thread.currentThread();
	//2. Get the map corresponding to the current thread
        ThreadLocalMap map = getMap(t);
        if (map != null)
	   //3. Store a key-value pair in the map
	   //this:threadLocal
	   //value: the stored copy
            map.set(this, value);
        else
            createMap(t, value);
    }

Each thread will have a corresponding map,map to store key-value pairs.

 

The use of ThreadLocal:

Case number one:

package com.huawei.Concurrency;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalDemo04 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(()-> {
                String data =  new ThreadLocalDemo04().date(finalI);
                System.out.println(Thread.currentThread().getName()+"->"+data);
            });
        }
        threadPool.shutdown();
    }
    private String date(int seconds){
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormater.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}
class ThreadSafeFormater{
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(()->
        new SimpleDateFormat("mm:ss"));
}

ThreadLocal maintains its own simpleDateFormat object for each thread, which is independent between threads and has nothing to do with each other. This also avoids thread safety issues. At the same time, the simpleDateFormat object will not be created too much. There are only 16 threads in the thread pool, so 16 objects are needed.

Case 2:

Information similar to global variables needs to be saved in each thread (such as user information obtained in the interceptor), which can be used directly by different methods, avoiding the trouble of parameter passing but not wanting to be shared by multiple threads (because the user information obtained by different threads information is different).

For example, use ThreadLocal to save some business content (user permission information, user name obtained from the user system, user ID, etc.), these information are the same in the same thread, but the business content used by different threads is different.

In the thread life cycle, the object that has been set by itself is obtained through the get() method of this static ThreadLocal instance, avoiding the trouble of passing this object (such as the user object) as a parameter.

For example, if we are a user system, when a request comes in, a thread will be responsible for executing the request, and then the request will sequentially call service-1(), service-2(), service-3(), service -4(), these 4 methods may be distributed in different classes.

package com.huawei.Concurrency;

public class ThreadLocalDemo05 {

    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }
}

class Service1{
    void service1(User user){
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}

class Service2{
    void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2 users who got it:"+user.name);
        new Service3().service3();
    }
}

class Service3{
    void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3 users who got it:"+user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder{
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class User{
    String name;
    public User(String name){
        this.name = name;
    }
}

Results of the:

User obtained by service2: jack

User obtained by service3: jack

 

ThreadLocal application scenarios# database connection:

 public Connection initialValue() {
 return DriverManager.getConnection(DB_URL);
 }
};  

public static Connection getConnection() {  
 return connectionHolder.get();
}  

ThreadLocal application scenarios # Session management:

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

ThreadLocal application scenarios# Multithreading:

public class ThreadLocalExsample {
​
 /**
 * An instance of MyRunnable is created and passed as a parameter to both threads. The two threads execute the run() method respectively,
 * And both hold different values ​​on the ThreadLocal instance. If they are not accessing ThreadLocal objects and the call to set() is synchronized,
 * Then the second thread will overwrite the value set by the first thread. However, since they access a ThreadLocal object,
 * So neither thread can see the value saved by the other. That is, they access two different values.
 */
 public static class MyRunnable implements Runnable {
 /**
 * A ThreadLocal object is instantiated. We only need to instantiate the object once, and we don't need to know which thread it was instantiated from.
 * Although all threads can access this ThreadLocal instance, each thread can only access its own by calling ThreadLocal
 * set()The value set by the method. Even if two different threads set different values ​​on the same ThreadLocal object,
 * They still don't have access to each other's values.
 */
 private ThreadLocal threadLocal = new ThreadLocal();
 @Override
 public void run() {
 //Once a ThreadLocal variable is created, you can set a value that needs to be saved with the following code
 threadLocal.set((int) (Math.random() * 100D));
 try {
 Thread.sleep(2000);
 } catch (InterruptedException e) {
 }
 //The value stored in the ThreadLocal variable can be read by the following methods
 System.out.println("-------threadLocal value-------"+threadLocal.get());
 }
 }
​
 public static void main(String[] args) {
 MyRunnable sharedRunnableInstance = new MyRunnable();
 Thread thread1 = new Thread(sharedRunnableInstance);
 Thread thread2 = new Thread(sharedRunnableInstance);
 thread1.start();
 thread2.start();
 }
}

operation result

-------threadLocal value-------38

-------threadLocal value-------88

Summarize:

There is a member variable threadLocals of type ThreadLocal.ThreadLocalMap in each thread Thread. This threadLocals is used to store the actual copy of the variable. The key value is the current ThreadLocal variable, and the value is the variable copy (that is, a variable of type T). Initially, in Thread, threadLocals is empty. When the get() method or set() method is called through the ThreadLocal variable, the threadLocals in the Thread class will be initialized, and the current ThreadLocal variable is used as the key value, and ThreadLocal is to be saved. The copy variable is value, which is stored in threadLocals. Then in the current thread, if you want to use the copy variable, you can find it in threadLocals through the get method.

The actual copy created via ThreadLocal is stored in each thread's own threadLocals;

Why is the key value of threadLocals type ThreadLocalMap a ThreadLocal object, because each thread can have multiple threadLocal variables, just like longLocal and stringLocal in the above code;

Before performing get, you must set first, otherwise a null pointer exception will be reported; if you want to access normally without calling set before get, you must rewrite the initialValue() method. Because in the above code analysis process, we found that if there is no set first, that is, the corresponding storage cannot be found in the map, i will be returned by calling the setInitialValue method, and in the setInitialValue method, there is a statement T value = initialValue(), and by default, the initialValue method returns null.

 

Tags: Java

Posted by Double_M on Tue, 03 May 2022 09:59:29 +0300