SoFunction
Updated on 2025-04-06

The problem of passing values ​​to child threads by Java multi-threaded parent thread and solving

1 Background

In the actual development process, we need to pass some data between parents and sons, such as user information, log asynchronous generation of data transmission, etc. This article solves the problem of data transmission between parents and sons from 5 solutions.

2 ThreadLocal+TaskDecorator

User Tools UserUtils

/**
  * Use ThreadLocal to store shared data variables, such as logged in user information
  */
public class UserUtils {
    private static  final  ThreadLocal<String> userLocal=new ThreadLocal<>();
 
    public static  String getUserId(){
        return ();
    }
    public static void setUserId(String userId){
        (userId);
    }
 
    public static void clear(){
        ();
    }
 
}

CustomTaskDecorator

/**
  * Thread pool modification class
  */
public class CustomTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // Get the request information in the main thread (our user information is also placed inside)        String robotId = ();
        (robotId);
        return () -> {
            try {
                // Set the request information of the main thread to the child thread                (robotId);
                // Execute child thread, don't forget this step                ();
            } finally {
                // End the thread, clear the information, otherwise it may cause memory leaks                ();
            }
        };
    }
}

ExecutorConfig

Add on the original basis (new CustomTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        ("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Use a thread pool that visualizes the running state        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //Configure the number of core threads        (corePoolSize);
        //Configure the maximum number of threads        (maxPoolSize);
        //Configure the queue size        (queueCapacity);
        //Configure the prefix of the thread name in the thread pool        (namePrefix);
 
        // rejection-policy: How to handle new tasks when the pool has reached max size        // CALLER_RUNS: Do not execute tasks in the new thread, but have the thread where the caller is located to execute        (new ());
 
        //Add thread pool modification class        (new CustomTaskDecorator());
        //Add MDC's thread pool modification class        //(new MDCTaskDecorator());
        //Execute initialization        ();
        ("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

    /**
      * Use ThreadLocal to pass
      * with return value
      * @throws InterruptedException
      */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync2() throws InterruptedException {
        ("start executeValueAsync");
        ("Async thread execution returns result...+");
        ("end executeValueAsync");
        return (());
    }
 

Test2Controller

    /**
      * How to use ThreadLocal+TaskDecorator
      * @return
      * @throws InterruptedException
      * @throws ExecutionException
      */
    @GetMapping("/test2")
    public String test2() throws InterruptedException, ExecutionException {
        ("123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync2();
        String s = ();
        return s;
    }

3 RequestContextHolder+TaskDecorator

CustomTaskDecorator

/**
  * Thread pool modification class
  */
public class CustomTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // Get the request information in the main thread (our user information is also placed inside)        RequestAttributes attributes = ();
        return () -> {
            try {
                // Set the request information of the main thread to the child thread                (attributes);
                // Execute child thread, don't forget this step                ();
            } finally {
                // End the thread, clear the information, otherwise it may cause memory leaks                ();
            }
        };
    }
}

ExecutorConfig

Add on the original basis (new CustomTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        ("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Use a thread pool that visualizes the running state        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //Configure the number of core threads        (corePoolSize);
        //Configure the maximum number of threads        (maxPoolSize);
        //Configure the queue size        (queueCapacity);
        //Configure the prefix of the thread name in the thread pool        (namePrefix);
 
        // rejection-policy: How to handle new tasks when the pool has reached max size        // CALLER_RUNS: Do not execute tasks in the new thread, but have the thread where the caller is located to execute        (new ());
 
        //Add thread pool modification class        (new CustomTaskDecorator());
        //Add MDC's thread pool modification class        //(new MDCTaskDecorator());
        //Execute initialization        ();
        ("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

     /**
      * Use RequestAttributes to get the data passed by the main thread
      * @return
      * @throws InterruptedException
      */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync3() throws InterruptedException {
        ("start executeValueAsync");
        ("Async thread execution returns result...+");
        RequestAttributes attributes = ();
        Object userId = ("userId", 0);
        ("end executeValueAsync");
        return (());
    }

Test2Controller

    /**
      * RequestContextHolder+TaskDecorator method
      * @return
      * @throws InterruptedException
      * @throws ExecutionException
      */
    @GetMapping("/test3")
    public String test3() throws InterruptedException, ExecutionException {
        RequestAttributes attributes = ();
        ("userId","123456",0);
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync3();
        String s = ();
        return s;
    }

4 MDC+TaskDecorator

Custom MDCTaskDecorator

/**
  * Thread pool modification class
  */
public class MDCTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // Get the request information in the main thread (our user information is also placed inside)        String userId = ("userId");
        Map<String, String> copyOfContextMap = ();
        (copyOfContextMap);
        return () -> {
            try {
                // Set the request information of the main thread to the child thread                ("userId",userId);
                // Execute child thread, don't forget this step                ();
            } finally {
                // End the thread, clear the information, otherwise it may cause memory leaks                ();
            }
        };
    }
}

ExecutorConfig

Add on the original basis (new MDCTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        ("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Use a thread pool that visualizes the running state        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //Configure the number of core threads        (corePoolSize);
        //Configure the maximum number of threads        (maxPoolSize);
        //Configure the queue size        (queueCapacity);
        //Configure the prefix of the thread name in the thread pool        (namePrefix);
 
        // rejection-policy: How to handle new tasks when the pool has reached max size        // CALLER_RUNS: Do not execute tasks in the new thread, but have the thread where the caller is located to execute        (new ());
 
        //Add MDC's thread pool modification class        (new MDCTaskDecorator());
        //Execute initialization        ();
        ("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

         /**
      * Use MDC to get the data passed by the main thread
      * @return
      * @throws InterruptedException
      */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync5() throws InterruptedException {
        ("start executeValueAsync");
        ("Async thread execution returns result...+");
        ("end executeValueAsync");
        return (("userId"));
    }

Test2Controller

     /**
      * Use MDC+TaskDecorator method
      * In essence, it is also the ThreadLocal+TaskDecorator method
      * @return
      * @throws InterruptedException
      * @throws ExecutionException
      */
    @GetMapping("/test5")
    public String test5() throws InterruptedException, ExecutionException {
        ("userId","123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync5();
        String s = ();
        return s;
    }

5 InheritableThreadLocal

Test code

public class TestThreadLocal {
	public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
	public static void main(String[] args) {
		 //Set thread variable        ("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //The child thread outputs the value of the thread variable                ("thread:"+());
            }
        });
        ();
        // The main thread outputs the value of the thread variable        ("main:"+());
	}
}

Output result:

main:hello world

thread:null

From the above results, we can see that after the same ThreadLocal variable is set in the parent thread, it cannot be obtained in the child thread;

The reason is that when the get method is called in the child thread thread, the current thread is the thread thread, and the main thread is called here to set the thread variable. The two are different threads, and naturally the child thread returns null when accessed.

In order to solve the above problem, InheritableThreadLocal came into being. InheritableThreadLocal inherits ThreadLocal, which provides a feature that allows child threads to access local variables set in the parent thread

Modify the above test code with InheritableThreadLocal

public class TestInheritableThreadLocal {
	
	public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
	
	public static void main(String[] args) {
		 //Set thread variable        ("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //The child thread outputs the value of the thread variable                ("thread:"+());
            }
        });
        ();
        // The main thread outputs the value of the thread variable        ("main:"+());
	}
}

Output result:

main:hello world

thread:hello world

5.1 Source code analysis

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return ;
    }
    void createMap(Thread t, T firstValue) {
         = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal rewrites three methods: childValue, getMap, and createMap

In InheritableThreadLocal, the variable inheritableThreadLocals replaces threadLocals;

So how can the child thread access the local variables of the parent thread. This starts with the code that creates Thread. Open the default constructor of the Thread class. The code is as follows:

  public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
         = name;
        //Get the current thread        Thread parent = currentThread();
       //If the inheritableThreadLocals variable of the parent thread is not null        if (inheritThreadLocals &amp;&amp;  != null)
        //Set child thread inheritThreadLocals variable             =
();
        /* Stash the specified stack size in case the VM cares */
         = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }

Let's look at the createInheritedMap code:

 =            ();

Create a new ThreadLocalMap variable using the inheritableThreadLocals variable of the parent thread as the constructor inside createInheritedMap, and then assign it to the inheritableThreadLocals variable of the child thread.

Let’s take a look at what is done inside the ThreadLocalMap constructor;

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = ;
            int len = ;
            setThreshold(len);
            table = new Entry[len];
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) ();
                    if (key != null) {
                        Object value = ();
                        Entry c = new Entry(key, value);
                        int h =  & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

The InheritableThreadLocal class is rewrite the following code

 ThreadLocalMap getMap(Thread t) {
       return ;
    }
    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
         = new ThreadLocalMap(this, firstValue);
    }

Let the local variable be saved into the inheritableThreadLocals variable of the specific thread. When the thread sets the variable through the set or get method of the InheritableThreadLocal class instance, it will create the inheritableThreadLocals variable of the current thread.

When the parent thread creates a child thread, the constructor will assign a copy of the local variable in the inheritableThreadLocals variable in the parent thread and save it to the inheritableThreadLocals variable of the child thread

5.2 Problems with InheritableThreadLocal

Although InheritableThreadLocal can solve the problem of obtaining the value of the parent thread in the child thread, when using a thread pool, since different tasks may be processed by the same thread, the values ​​obtained by these tasks may not be the values ​​set by the parent thread when using a thread pool.

Test objective: Task 1 and Task 2 get the same value as the parent thread, which is the hello world in the test code

Test code:

public class TestInheritableThreadLocaIssue {
	
	public static InheritableThreadLocal&lt;String&gt; threadLocal = new InheritableThreadLocal&lt;&gt;();
	public static ExecutorService executorService = ();
	
	public static void main(String[] args) throws Exception {
		 //Set thread variable        ("hello world");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //The child thread outputs the value of the thread variable                ("thread:"+());
                ("hello world 2");
            }
        },"task1");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //The child thread outputs the value of the thread variable                ("thread:"+());
                ("hello world 2");
            }
        },"task2");
        (thread1).get();
        (thread2).get();
        
        // The main thread outputs the value of the thread variable        ("main:"+());
	}
}

Output result:

thread:hello world

thread:hello world 2

main:hello world

Results analysis:

It is obvious that Task 2 does not obtain hello world set by the parent thread, but the modified value of thread 1. If used in thread pool, you need to pay attention to this situation (you can back up the value of the parent thread)

6 TransmittableThreadLocal

Solve thread pooled value delivery

Alibaba has encapsulated a tool to provide ThreadLocal value delivery function when using thread pools and other components that use thread pools to multiplex threads, to solve the problem of context delivery during asynchronous execution.

JDK's InheritableThreadLocal class can complete the value passing of the parent thread to the child thread. However, for the case where the execution components that use thread pools and other threads are pooled and multiplexed, the threads are created by the thread pool, and the threads are pooled and used repeatedly;

At this time, the ThreadLocal value passed in the parent-child thread relationship is meaningless. What the application needs is actually to pass the ThreadLocal value when submitting the task to the thread pool to the task execution time

/alibaba/transmittable-thread-local

Introduced:

<dependency>
	<groupId></groupId>
	<artifactId>transmittable-thread-local</artifactId>
	<version>2.11.5</version>
</dependency>

Requirement scenario:

  • 1. Distributed tracking system or full-link pressure measurement (i.e. link marking)
  • 2. Log collection and recording system context
  • Level Cache
  • 4. Application container or upper framework passes information to the lower SDK across application code

Test code:

1) Parent-son thread information transmission

public static TransmittableThreadLocal&lt;String&gt; threadLocal = new TransmittableThreadLocal&lt;&gt;();
	
	public static void main(String[] args) {
		 //Set thread variable        ("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //The child thread outputs the value of the thread variable                ("thread:"+());
            }
        });
        ();
        // The main thread outputs the value of the thread variable        ("main:"+());
	}
}

Output result:

main:hello world

thread:hello world

2) Pass values ​​in thread pool, refer to github:Modify thread pool

Summarize

The above is personal experience. I hope you can give you a reference and I hope you can support me more.