Java multi-threaded parent thread passing value to child thread solution


1 background

In the actual development process, we need to transfer some data between father and son, such as user information, log asynchronously generated data transfer, etc. This article solves the problem of data transfer between father and son from 5 solutions

Insert image description here

2 ThreadLocal+TaskDecorator

User Utils Class UserUtils

/**
 *使用ThreadLocal存储共享的数据变量,如登录的用户信息
 */
public class UserUtils {
    
    
    private static  final  ThreadLocal<String> userLocal=new ThreadLocal<>();
 
    public static  String getUserId(){
    
    
        return userLocal.get();
    }
    public static void setUserId(String userId){
    
    
        userLocal.set(userId);
    }
 
    public static void clear(){
    
    
        userLocal.remove();
    }
 
}

Custom CustomTaskDecorator

/**
 * 线程池修饰类
 */
public class CustomTaskDecorator implements TaskDecorator {
    
    
    @Override
    public Runnable decorate(Runnable runnable) {
    
    
        // 获取主线程中的请求信息(我们的用户信息也放在里面)
        String robotId = UserUtils.getUserId();
        System.out.println(robotId);
        return () -> {
    
    
            try {
    
    
                // 将主线程的请求信息,设置到子线程中
                UserUtils.setUserId(robotId);
                // 执行子线程,这一步不要忘了
                runnable.run();
            } finally {
    
    
                // 线程结束,清空这些信息,否则可能造成内存泄漏
                UserUtils.clear();
            }
        };
    }
}

ExecutorConfig

Add executor.setTaskDecorator(new CustomTaskDecorator()); on the original basis;

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
    
    
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可视化运行状态的线程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加线程池修饰类
        executor.setTaskDecorator(new CustomTaskDecorator());
        //增加MDC的线程池修饰类
        //executor.setTaskDecorator(new MDCTaskDecorator());
        //执行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

    /**
     * 使用ThreadLocal方式传递
     * 带有返回值
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync2() throws InterruptedException {
    
    
        log.info("start executeValueAsync");
        System.out.println("异步线程执行返回结果......+");
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(UserUtils.getUserId());
    }
 

Test2Controller

    /**
     * 使用ThreadLocal+TaskDecorator的方式
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @GetMapping("/test2")
    public String test2() throws InterruptedException, ExecutionException {
    
    
        UserUtils.setUserId("123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync2();
        String s = completableFuture.get();
        return s;
    }

3 RequestContextHolder+TaskDecorator

Custom CustomTaskDecorator

/**
 * 线程池修饰类
 */
public class CustomTaskDecorator implements TaskDecorator {
    
    
    @Override
    public Runnable decorate(Runnable runnable) {
    
    
        // 获取主线程中的请求信息(我们的用户信息也放在里面)
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return () -> {
    
    
            try {
    
    
                // 将主线程的请求信息,设置到子线程中
                RequestContextHolder.setRequestAttributes(attributes);
                // 执行子线程,这一步不要忘了
                runnable.run();
            } finally {
    
    
                // 线程结束,清空这些信息,否则可能造成内存泄漏
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

ExecutorConfig

Add executor.setTaskDecorator(new CustomTaskDecorator()); on the original basis;

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
    
    
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可视化运行状态的线程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加线程池修饰类
        executor.setTaskDecorator(new CustomTaskDecorator());
        //增加MDC的线程池修饰类
        //executor.setTaskDecorator(new MDCTaskDecorator());
        //执行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

     /**
     * 使用RequestAttributes获取主线程传递的数据
     * @return
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync3() throws InterruptedException {
    
    
        log.info("start executeValueAsync");
        System.out.println("异步线程执行返回结果......+");
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        Object userId = attributes.getAttribute("userId", 0);
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(userId.toString());
    }
 

Test2Controller

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

4 MDC+TaskDecorator

Custom MDCTaskDecorator

/**
 * 线程池修饰类
 */
public class MDCTaskDecorator implements TaskDecorator {
    
    
    @Override
    public Runnable decorate(Runnable runnable) {
    
    
        // 获取主线程中的请求信息(我们的用户信息也放在里面)
        String userId = MDC.get("userId");
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        System.out.println(copyOfContextMap);
        return () -> {
    
    
            try {
    
    
                // 将主线程的请求信息,设置到子线程中
                MDC.put("userId",userId);
                // 执行子线程,这一步不要忘了
                runnable.run();
            } finally {
    
    
                // 线程结束,清空这些信息,否则可能造成内存泄漏
                MDC.clear();
            }
        };
    }
}

ExecutorConfig

Add executor.setTaskDecorator(new MDCTaskDecorator()); on the original basis;

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
    
    
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可视化运行状态的线程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加MDC的线程池修饰类
        executor.setTaskDecorator(new MDCTaskDecorator());
        //执行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

         /**
     * 使用MDC获取主线程传递的数据
     * @return
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync5() throws InterruptedException {
    
    
        log.info("start executeValueAsync");
        System.out.println("异步线程执行返回结果......+");
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(MDC.get("userId"));
    }

Test2Controller

     /**
     * 使用MDC+TaskDecorator方式
     * 本质也是ThreadLocal+TaskDecorator方式
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @GetMapping("/test5")
    public String test5() throws InterruptedException, ExecutionException {
    
    
        MDC.put("userId","123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync5();
        String s = completableFuture.get();
        return s;
    }

5 InheritableThreadLocal

test code

public class TestThreadLocal {
    
    
	public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
	public static void main(String[] args) {
    
    
		 //设置线程变量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run( ) {
    
    
                //子线程输出线程变量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主线程输出线程变量的值
        System.out.println("main:"+threadLocal.get());
	}
}

Output result:

main:hello world
thread:null

It can be seen from the above results 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 sub-thread thread, the current thread is the thread thread, and the set method here to set the thread variable is the main thread. The two are different threads. Naturally, null is returned when the sub-thread accesses it.

In order to solve the above problem, InheritableThreadLocal came into being. InheritableThreadLocal inherits ThreadLocal and 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) {
    
    
		 //设置线程变量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run( ) {
    
    
                //子线程输出线程变量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主线程输出线程变量的值
        System.out.println("main:"+threadLocal.get());
	}
}

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 t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
    
    
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal overrides the three methods childValue, getMap, and createMap
. In InheritableThreadLocal, the variable inheritableThreadLocals replaces threadLocals;

So how to allow the child thread to access the local variables of the parent thread. This starts with the code to create 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");
        }
        this.name = name;
        //获取当前线程
        Thread parent = currentThread();
       //如果父线程的 inheritableThreadLocals变量不为null
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //设置子线程inheritThreadLocals变量
            this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }

Let’s take a look at the createInheritedMap code:

this.inheritableThreadLocals =            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

Inside createInheritedMap, a new ThreadLocalMap variable is created using the inheritableThreadLocals variable of the parent thread as the constructor, and then assigned to the inheritableThreadLocals variable of the child thread. Let's take a look at what is done inside the constructor of ThreadLocalMap;

private ThreadLocalMap(ThreadLocalMap parentMap) {
    
    
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            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>) e.get();
                    if (key != null) {
    
    
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

The InheritableThreadLocal class rewrites the following code

 ThreadLocalMap getMap(Thread t) {
    
    
       return t.inheritableThreadLocals;
    }
    /**
     * 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) {
    
    
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

Let the local variables be saved in the inheritableThreadLocals variable of the specific thread. Then when the thread sets the variable through the set or get method of the InheritableThreadLocal class instance, the inheritableThreadLocals variable of the current thread will be created.

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 the thread pool, since different tasks may be processed by the same thread, the values ​​obtained by these tasks may not be the parent thread. Set value
Test target: Task 1 and Task 2 obtain the same parent thread value, which is the hello world test code in the test code
:

public class TestInheritableThreadLocaIssue {
    
    
	
	public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
	public static ExecutorService executorService = Executors.newSingleThreadExecutor();
	
	public static void main(String[] args) throws Exception {
    
    
		 //设置线程变量
        threadLocal.set("hello world");
        Thread thread1 = new Thread(new Runnable() {
    
    
            @Override
            public void run( ) {
    
    
                //子线程输出线程变量的值
                System.out.println("thread:"+threadLocal.get());
                threadLocal.set("hello world 2");
            }
        },"task1");
        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run( ) {
    
    
                //子线程输出线程变量的值
                System.out.println("thread:"+threadLocal.get());
                threadLocal.set("hello world 2");
            }
        },"task2");
        executorService.submit(thread1).get();
        executorService.submit(thread2).get();
        
        // 主线程输出线程变量的值
        System.out.println("main:"+threadLocal.get());
	}
}

Output result:

thread:hello world
thread:hello world 2
main:hello world

Result analysis:
Obviously, task 2 does not obtain the hello world set by the parent thread, but the modified value of thread 1. If used in a thread pool, you need to pay attention to this situation (you can back up the value of the parent thread)

6 TransmittableThreadLocal

Solving thread pooling value transfer

Alibaba has encapsulated a tool that provides the ThreadLocal value transfer function when using thread pools and other components that pool and reuse threads, solving the problem of context transfer during asynchronous execution.

JDK's InheritableThreadLocal class can complete the value transfer from parent thread to child thread. However, when using thread pools and other execution components that pool and reuse threads, the threads are created by the thread pool, and the threads are pooled and used repeatedly; at this time, the ThreadLocal value transfer of the parent-child thread relationship is meaningless, and the application needs In fact, the ThreadLocal value when submitting the task to the thread pool is passed to the task execution time
https://github.com/alibaba/transmittable-thread-local
. Introduction:

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

Demand scenarios:
1. Distributed tracking system or full-link stress testing (i.e. link marking)
2. Log collection and recording system context
3. Session-level Cache
4. Application container or upper-layer framework transmits information to the lower-layer SDK across application codes

Test code:
1) Parent-child thread information transfer

public static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
	
	public static void main(String[] args) {
    
    
		 //设置线程变量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run( ) {
    
    
                //子线程输出线程变量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主线程输出线程变量的值
        System.out.println("main:"+threadLocal.get());
	}
}

Output result:

main:hello world
thread:hello world

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

おすすめ

転載: blog.csdn.net/ZGL_cyy/article/details/132702802