Author: Tangyuan

Personal blog: Javalover.cc

preface

Previously in thread security introduced global variables (member variables) and local variables (variables in methods or code blocks), the former in multithreading is not safe, need to lock and other mechanisms to ensure safety, the latter is thread-safe, but can not be shared between multiple methods

Today’s hero, ThreadLocal, fills in the gap between global and local variables

Introduction to the

ThreadLocal has two main functions:

  1. Data isolation between threads: One copy is created for each thread, and threads cannot access each other

  2. Simplification of parameter passing: Copies created for each thread are globally visible within a single thread and do not need to be passed between methods

In fact, the above two effects, in the final analysis are due to the credit of the replica, that is, each thread creates a separate replica, to produce the above effect

ThreadLocal is literally a thread-local variable, a clever combination of both global and local variables

Here are two examples of how it works

directory

  1. Example – Data isolation
  2. Example – Parameter transfer optimization
  3. internals

The body of the

When we come into contact with something new, we should first use it and then explore the inner workings

Thread Local is relatively simple to use, like Map, various put/get

Its core approach is as follows:

  • public void set(T value): Stores the current copy in ThreadLocal, separately for each thread
  • public T get(): Retrieves the saved copy. Each thread retrieves only its own copy
  • protected T initialValue()InitialValue initialValue initialValue initialValue initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy initialCopy
  • public void remove(): Deletes the copy you just saved

1. Example – Data isolation

We use SimpleDateFormat as an example, because this class is thread-unsafe and can cause all kinds of concurrency problems without isolation

Let’s take a look at a thread unsafe example. The code is as follows:

public class ThreadLocalDemo {

    // Thread unsafe: Parsing errors are possible when executing in multiple threads
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    public void parse(String dateString){
        try {
            System.out.println(simpleDateFormat.parse(dateString));
        } catch(ParseException e) { e.printStackTrace(); }}public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 30; i++) {
            service.execute(()->{
                demo.parse("2020-01-01"); }); }}}Copy the code

If you run it more than once, the following error may occur:

Exception in thread "pool-1-thread-4" java.lang.NumberFormatException: empty String
Copy the code

SimpleDateFormat’s security concerns are addressed in the source code comments as follows:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
Copy the code

When using multiple threads, it is recommended that each thread be created separately or locked

Let’s use locking and separate creation to solve the problem

Example of thread safety: locking

public class ThreadLocalDemo {

    // Thread safety 1: add built-in lock
    private SimpleDateFormat simpleDateFormatSync = new SimpleDateFormat("yyyy-MM-dd");
    public void parse1(String dateString){
        try {
           synchronized(simpleDateFormatSync){ System.out.println(simpleDateFormatSync.parse(dateString)); }}catch(ParseException e) { e.printStackTrace(); }}public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 30; i++) {
            service.execute(()->{
                demo.parse1("2020-01-01"); }); }}}Copy the code

A thread-safe example: Create a copy for each thread with ThreadLocal

public class ThreadLocalDemo {

    // Thread safety 2: create a copy of the object with ThreadLocal for data isolation
    // This code can be simplified by using withInitialValue
    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
        // Initialize the method once per thread; For example, if the thread pool has 10 threads, the total number of copies of SimpleDateFormat is only 10, no matter how many times it is run
        @Override
        protected SimpleDateFormat initialValue(a) {
            // This will output 10 times, respectively the id of each thread
            System.out.println(Thread.currentThread().getId());
            return new SimpleDateFormat("yyyy-MM-dd"); }};public void parse2(String dateString){
        try {
            System.out.println(threadLocal.get().parse(dateString));
        } catch(ParseException e) { e.printStackTrace(); }}public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 30; i++) {
            service.execute(()->{
                demo.parse2("2020-01-01"); }); }}}Copy the code

Some of you might wonder, why doesn’t this example just create local variables?

This is because if you create a local variable, a single call will create a SimpleDateFormat, and performance will be low

By creating a copy for each thread with ThreadLocal, all subsequent operations on that thread access that copy without having to create it again

2. Example – Parameter transfer optimization

Sometimes, when we need to pass parameters between multiple methods (such as user information), we face a problem:

  • If the parameters to be passed are set to global variables, then the thread is not safe
  • If the parameters to be passed are set to local variables, passing can be cumbersome

This is where ThreadLocal comes in. As mentioned earlier, ThreadLocal’s job is to combine global and local advantages, making it thread-safe and easy to pass parameters

Here’s an example:

public class ThreadLocalDemo2 {

    // The parameter is passed
    public void fun1(int age){
        System.out.println(age);
        fun2(age);
    }
    private void fun2(int age){
        System.out.println(age);
        fun3(age);
    }
    private void fun3(int age){
        System.out.println(age);
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadLocalDemo2 demo = new ThreadLocalDemo2();
        for (int i = 0; i < 30; i++) {
            final intj = i; service.execute(()->{ demo.fun1(j); }); }}}Copy the code

This code may not make any real sense, but it should, which is to express the complexity of passing parameters

Let’s look at using ThreadLocal to solve this problem

public class ThreadLocalDemo2 {

    ThreadLocal is used as a global variable
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    public void fun11(a){
        System.out.println(threadLocal.get());
        fun22();
    }
    private void fun22(a){
        System.out.println(threadLocal.get());
        fun33();
    }
    private void fun33(a){
        int age = threadLocal.get();
        System.out.println(age);
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadLocalDemo2 demo = new ThreadLocalDemo2();
        for (int i = 0; i < 30; i++) {
            final int j = i;
            service.execute(()->{
                try{
                    threadLocal.set(j);
                    demo.fun11();
                }finally{ threadLocal.remove(); }}); }}}Copy the code

As you can see, instead of passing the age argument around, we create a copy of age for each thread

This way all methods can access the copy, and it also keeps thread-safe

The remove method is used to remove a copy of the set. This will be described below

3. Internal principles

How does it isolate data

Let’s look at the set method first:

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

As you can see, the values are stored in the map (key is a ThreadLocal object and value is a separate copy created for the thread).

And where did this map come from? Take a look at the following code

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

As you can see, the Thread ends up inside the Thread, which is why isolation is implemented between threads and sharing is implemented within threads (since they are properties within threads, only the current Thread is visible).

Let’s look at the get() method as follows:

public T get(a) {
    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;
            returnresult; }}return setInitialValue();
}
Copy the code

As you can see, the map in the current thread is found, and then the value is fetched based on the key

The setInitialValue in the last line is the initialization action to be performed again if GET is empty

Why use ThreadLocal as the key instead of the thread ID

To store multiple variables

If the thread ID is used as the key, only one variable can be stored per thread in the map

With ThreadLocal as the key, you can store multiple variables in one thread (by creating multiple ThreadLocal)

As follows:

private static ThreadLocal<Integer> threadLocal1 = new ThreadLocal<Integer>();
private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<Integer>();

public void test(a){
    threadLocal1.set(1);
    threadLocal2.set(2);
    System.out.println(threadLocal1.get());
    System.out.println(threadLocal2.get());
}
Copy the code

Let’s talk about memory leaks

Let’s take a look at the internal ThreadLocalMap code:

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<? >>{
        /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}}Copy the code

As you can see, the internal node Entry inherits weak references (in garbage collection, if an object has only weak references, it will be recycled) and then sets the key to weak references in the constructor via super(k)

Therefore, if there is no strong external reference to ThreadLocal during garbage collection, the key is simply recycled

Key =null, value is still there, but you can’t get it out. Over time, problems will occur

The solution is remove, which removes the copy from the ThreadLocal by removing it in finally, with both the key and value removed

conclusion

  1. ThreadLocal, literally a thread-local variable, ensures data isolation between threads and simplifies parameter passing between methods by creating a separate copy for each thread
  2. The essence of data isolation: Threads hold the ThreadLocalMap object internally, where all copies are created, so each Thread is isolated from each other
  3. Memory leak problem: The key in a ThreadLocalMap is a weak reference, so if the object to which the key points does not have a strong reference, then the garbage collector will collect the value. The value will still exist, but will not be retrieved.
  4. Usage scenario: interview and other occasions

Reference content:

  • Real Java High Concurrency
  • Liao Xuefeng ThreadLocal:www.liaoxuefeng.com/wiki/125259…

Afterword.

In fact, here is not very in-depth to analyze part of the source code knowledge, mainly energy and ability is limited, and then slowly go deep