多线程中的ThreadLocal的传递问题以及LOG的MDC的使用

文章介绍了在日志中使用MDC记录RequestID来追踪请求,通过拦截器或过滤器设置MDC并在多线程环境中,特别是在线程池中,如何利用InheritableThreadLocal保持MDC的上下文信息。同时,提到了线程池中装饰器的作用以及对Request生命周期的理解。
摘要由CSDN通过智能技术生成

多线程中的ThreadLocal的传递问题以及LOG的MDC的使用

Log的MDC使用场景:

日志文件的RequsetId的打印,使用%X[RequestId]进行打印,但是接口层在发生异常的时候,会出现日志打印的%X[RequestId]的值为空的情况;

问题处理:

1、使用拦截器或者过滤器,增加MDC.put(RequestHeader.REQUEST_ID, values);
需要注意的是MDC引入的包是org.slf4j.MDC,log的文件使用log4j2.xml

import com.m.kit.common.constant.RequestHeader;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class MDCFilter implements Filter {


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String values = httpRequest.getHeader("RequestId");
        // key必须与log4j2.xml的配置文件的%X{}里的对应
        MDC.put("RequestId", values);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

2、如果你需要在多线程里日志打印也带上请求ID,则这个就与ThreadLocal有关,可以进行如下配置:在resource内增加log4j2.component.properties文件并增加如下配置

# 设置多线程的情况下,MDC也进行设置在子线程里面
isThreadContextMapInheritable=true

注意点:单这样配置还不完全行,倘如多线程使用的是线程池,你会发现,后续的多线程的日志打印的RequestId的值是不变的,因为线程池的线程是复用的,如需改变,则需要往下看

线程池的多线程的ThreadLocal等的传递问题

如需要进行传递到子线程,可以在线程池的配置中加入装饰器
注意:将Request在装饰器上进程传递的话,又可能出现子线程拿到的request信息为null的情况,这是由于request的生命周期的问题,所以不建议将request放进

mport java.util.Set;

public class ThreadLocalPermissionSet {

    private ThreadLocalPermissionSet (){

    }

    private static final InheritableThreadLocal<Set<String>> PERMISSION_SET = new InheritableThreadLocal<>();

    public static Set<String> getPermissionSet() {
        return PERMISSION_SET.get();
    }

    public static void setPermissionSet(Set<String> permissionSet) {
        PERMISSION_SET.set(permissionSet);
    }

    public static void removePermissionSet() {
        PERMISSION_SET.remove();
    }
}
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

import java.util.Map;


public class MyTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
    	// 不建议使用Request的,因为生命周期问题
    	// RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    	Set<String> permissionSet = ThreadLocalPermissionSet.getPermissionSet();
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return () -> {
            try {
            	// RequestContextHolder.setRequestAttributes(requestAttributes);
            	ThreadLocalPermissionSet.setPermissionSet(permissionSet );
                MDC.setContextMap(contextMap);
                runnable.run();
            } finally {
            	// RequestContextHolder.resetRequestAttributes();
            	ThreadLocalPermissionSet.removePermissionSet();
                MDC.clear();
            }
        };
    }

}
    @Bean(name = "myExecutorService")
    protected Executor myExecutorService() {
        ThreadPoolTaskExecutor myExecutor = new ThreadPoolTaskExecutor();
        myExecutor.setCorePoolSize(8);
        myExecutor.setMaxPoolSize(16);
        myExecutor.setQueueCapacity(64);
        myExecutor.setThreadNamePrefix("myExecutorService_");
        myExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        myExecutor.setTaskDecorator(new MyTaskDecorator());
        myExecutor.initialize();
        return myExecutor ;
    }
}

注意点:ThreadLocal是不可以继承的,如果需要传递ThreadLocal的值,可以使用其子类InheritableThreadLocal或者自己继承实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多线程环境,我们可以使用ThreadLocal类来实现线程局部变量。ThreadLocal为每个线程提供了一个独立的存储空间,每个线程都可以独立地修改自己的副本,互不干扰。 要在多线程环境使用ThreadLocal,我们需要执行以下步骤: 1. 创建ThreadLocal对象:通过创建ThreadLocal对象来保存线程的局部变量。例如,可以使用`ThreadLocal<Integer>`来保存一个整数类型的线程局部变量。 2. 设置线程局部变量:在每个线程,通过调用ThreadLocal对象的`set()`方法来设置该线程的局部变量的值。例如,使用`threadLocal.set(value)`来设置线程局部变量的值为value。 3. 获取线程局部变量:在每个线程,通过调用ThreadLocal对象的`get()`方法来获取该线程的局部变量的值。例如,使用`threadLocal.get()`来获取线程局部变量的值。 4. 移除线程局部变量(可选):如果需要,在每个线程结束时,可以通过调用ThreadLocal对象的`remove()`方法来移除该线程的局部变量。例如,使用`threadLocal.remove()`来移除线程局部变量。 下面是一个示例代码,演示如何在多线程环境使用ThreadLocal: ```java public class ThreadLocalExample { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { threadLocal.set(1); System.out.println("Thread 1: " + threadLocal.get()); threadLocal.remove(); }); Thread thread2 = new Thread(() -> { threadLocal.set(2); System.out.println("Thread 2: " + threadLocal.get()); threadLocal.remove(); }); thread1.start(); thread2.start(); } } ``` 这个示例,我们创建了一个ThreadLocal对象来保存整数类型的线程局部变量。在每个线程,我们使用`threadLocal.set()`方法来设置线程局部变量的值,并使用`threadLocal.get()`方法来获取线程局部变量的值。最后,我们调用`threadLocal.remove()`方法来移除线程局部变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值