Do you know ThreadLocal? What's the use of talking together

华为云开发者社区
中文
Abstract: ThreadLocal is a class provided by java to facilitate the transfer and acquisition of objects in different methods in this thread. The variables defined by it are only visible and maintained in this thread, not affected by other threads, and isolated from other threads.

This article is shared from Huawei Cloud Community " ThreadLocal: Thread-specific Variable ", author: zuozewei.

Introduction to ThreadLocal

ThreadLocal is a class provided by java to facilitate the transfer and acquisition of objects in different methods in this thread. are only visible and maintained in this thread, not affected by other threads, and isolated from other threads.

So what problem does ThreadLocal solve, and what scenarios does it apply to?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

The core meaning is

ThreadLocal provides a thread local instance. The difference between it and ordinary variables is that each thread that uses the variable initializes a completely independent copy of the instance. ThreadLocal variables are usually decorated by private static. When a thread ends, all ThreadLocal instance copies used by it can be recycled.

In general, ThreadLocal is suitable for scenarios where each thread needs its own independent instance and the instance needs to be used in multiple methods, that is, variables are isolated between threads and shared between methods or classes . This point of view will be elaborated in detail later through examples. In addition, in this scenario, it is not necessary to use ThreadLocal, other methods can achieve the same effect, but ThreadLocal makes the implementation more concise.

ThreadLocal use

ThreadLocal can assign values to variables through the set method, and get the value of variables through the get method. Of course, you can also assign an initial value to the variable through the ThreadLocal.withInitial method when defining the variable, or define a class that inherits ThreadLocal, and then override the initialValue method.

The following code illustrates the use of ThreadLocal:

public class TestThreadLocal
{
    private static ThreadLocal<StringBuilder> builder = ThreadLocal.withInitial(StringBuilder::new);

    public static void main(String[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            new Thread(() -> {
                String threadName = Thread.currentThread().getName();
                for (int j = 0; j < 3; j++)
                {
                    append(j);
                    System.out.printf("%s append %d, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, j, builder.get().toString(), builder.hashCode(), builder.get().hashCode());
                }

                change();
                System.out.printf("%s set new stringbuilder, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, builder.get().toString(), builder.hashCode(), builder.get().hashCode());
            }, "thread-" + i).start();
        }
    }

    private static void append(int num) {
        builder.get().append(num);
    }

    private static void change() {
        StringBuilder newStringBuilder = new StringBuilder("HelloWorld");
        builder.set(newStringBuilder);
    }
}

In the example, a builder's ThreadLocal object is defined, and then 5 threads are started to access and modify the builder object respectively. These two operations are carried out in two different functions append and change, and the two functions access the builder Objects are also obtained directly, instead of being passed into the input parameters of the function.

The code output is as follows:

thread-0 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654
thread-0 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654
thread-4 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086
thread-3 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945
thread-2 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498
thread-1 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830
thread-2 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498
thread-3 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945
thread-4 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086
thread-0 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654
thread-0 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1773033190
thread-4 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086
thread-4 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 700642750
thread-3 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945
thread-3 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1706743158
thread-2 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498
thread-2 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1431127699
thread-1 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830
thread-1 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830
thread-1 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1970695360
  • It can be seen from lines 1 to 6 in the output that different threads access the same builder object (the ThreadLocal instance hashcode value output by different threads is the same), but the instance StringBuilder stored by the builder object obtained by each thread is different (output by different threads ThreadLocal instance mapping value hashcode values are not the same).
  • It can be seen from lines 1~2, 9~10 in the output that when the value of the instance stored by the builder object is modified in the same thread, it will not affect the instance stored by the builder object of other threads (thread-4 thread changes the stored instance). The value of StringBuilder does not cause the ThreadLocal instance mapping value hashcode value of thread-0 thread to change)
  • It can be seen from lines 9 to 13 in the output that when a thread changes the value stored in the ThreadLocal object, it will not affect other threads (thread-0 thread calls the set method to change the object value stored in the thread ThreadLocal The ThreadLocal instance mapping value hashcode has changed, but the ThreadLocal instance mapping value hashcode of thread-4 has not changed accordingly).

ThreadLocal principle

ThreadLocal can be isolated between each thread. is mainly achieved by maintaining a ThreadLocalMap in each Thread object. is an object in a thread, it is invisible to other threads, thus achieving the purpose of isolation. So why is it a Map structure? The main reason is that there may be multiple ThreadLocal objects in a thread, which requires a collection for storage distinction, and Map can be used to find related objects faster.
ThreadLocalMap is a static internal class of the ThreadLocal object. It maintains an Entry array internally to implement operations like get and put of Map. For simplicity, it can be regarded as a Map, where key is an instance of ThreadLocal and value is an instance of ThreadLocal. The stored value.
image.png

ThreadLocal applicable scenarios

As mentioned above, ThreadLocal is suitable for the following scenarios:

  • Each thread needs to have its own separate instance, such as the realization of each thread singleton class or each thread context information (such as transaction ID).
  • ThreadLocal is suitable for scenarios where variables are isolated between threads and shared between methods, and provides another way to extend Thread. If you want to retain information or pass information from one method call to another, you can use ThreadLocal to pass it.
  • Since there is no need to modify any method, it can provide great flexibility.

Case number one

Here is a class that handles flags, used by ThreadLocal, to ensure that each request has a unique trace flag.

public class TestFlagHolder {

  private final static ThreadLocal<String> TEST_FLAG = new ThreadLocal<>();

  public static void set(String value) {
    TEST_FLAG.set(value);
  }

  public static String get() {
    return TEST_FLAG.get();
  }

  public static String get4log() {
    if (TEST_FLAG.get() == null) {
      return "-";
    }
    return TEST_FLAG.get();
  }

  public static void remove() {
    TEST_FLAG.remove();
  }

}

Case two

Transmission of trace information in the same thread:

ThreadLocal<String> traceContext = new ThreadLocal<>();

String traceId = Tracer.startServer();
traceContext.set(traceId) //生成trace信息 传入threadlocal
...
Tracer.startClient(traceContext.get()); //从threadlocal获取trace信息
Tracer.endClient();
...
Tracer.endServer();

Case three

Add the same mark to each log line of the same request. In this way, as long as you get this tag, you can query the time consumption of all steps on the request link. We call this tag requestId. We can generate a requestId at the entrance of the program and put it in the context of the thread. , So that the requestId can be obtained from the thread context at any time when needed.

The simple code implementation is as follows:

String requestId = UUID.randomUUID().toString();
ThreadLocal<String> tl = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return requestId;
    }
}; //requestId存储在线程上下文中
long start = System.currentTimeMillis();
processA();
Logs.info("rid : " + tl.get() + ", process A cost " + (System.currentTimeMillis() - start)); // 日志中增加requestId
start = System.currentTimeMillis();
processB();
Logs.info("rid : " + tl.get() + ", process B cost " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
processC();
Logs.info("rid : " + tl.get() + ", process C cost " + (System.currentTimeMillis() - start));

With requestId, you can clearly understand the time-consuming distribution of a call link.

summary

Whether it is a monolithic system or a micro-service architecture, whether it is link marking or service tracking, you need to add TraceId to the system, so that you can link your links together and present you with a complete problem scenario. If the TraceId can be generated on the client and passed to the server when requesting the business interface, then the log system of the client can also be integrated, which is more helpful for troubleshooting.

At the same time, in the full-link stress test framework, the transmission function of Trace information is based on ThreadLocal. However, asynchronous calls may be used in actual business, so that trace information will be lost and the integrity of the link will be destroyed. Therefore, it is recommended that you do not use ThreadLocal lightly in actual projects.

Reference materials:

[1]: "40 Questions about High Concurrency System Design"
[2]:https://juejin.cn/post/6844904016288317448#heading-6

Click to follow, and learn about Huawei Cloud's fresh technology for the first time~

阅读 499

开发者之家
华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态...

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态...

1.2k 声望
1.7k 粉丝
0 条评论

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态...

1.2k 声望
1.7k 粉丝
文章目录
宣传栏