线程安全—ThreadLocal

定义:

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为每个线程都创建了一个副本,存的是自己,那么每个线程可以访问自己内部的副本变量。

ThreadLocal是用空间换取时间,synchronized关键字是用时间换空间。

ThreadLocal操作值的时候是取得当前线程的ThreadLocalMap对象,然后把值设置到了这个对象中,这样对于不同的线程得到的就是不同的ThreadLocalMap,那么向其中保存值或者修改值都只是会影响到当前线程,这样就保证了线程安全。

使用:

由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法:
每次使用完ThreadLocal都调用它的remove()方法清除数据
将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离,【你访问你的,我访问我的,不会相互干扰】

3、进行事务操作,用于存储线程事务信息。

4、数据库连接,Session会话管理。

5、MDC链路追踪。

如何解决线程池中的值传递功能?(ThreadLocal,InheritableThreadLocal ,TransmittableThreadLocal区别)
InheritableThreadLocal 继承 ThreadLocal,主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。

由于InheritableThreadLocal是在异步子线程初始化时进行的设置,而线程只会初始化一次,就会导致子线程中的ThreadLocal一直是同一个。

开发中,一般会用池化的技术来管理多个线程,即会用到线程池。在线程池中,核心线程一般来说是不会死的,他们会被反反复复使用。用InheritableThreadLocal的问题在于,threadLocal只在子线程初始化的时候进行了传递,当这个子线程完成了第一个任务,随后处理其他请求时,这个子线程中的threadLocal却还一直是第一个请求时设置的那个threadLocal,就会导致出错。

在遇到线程池等会池化复用线程的执行组件情况下,就需要通过TransmittableThreadLocal类来实现线程值传递。

MDC链路追踪如何使用ThreadLocal?

异步服务的统一API,例如:

    @Override
    @Async
    public  void execute(Map logContext, Consumer consumer, T t) {
        this.dealLogContext(logContext);
        LOGGER.debug("发起异步调用,设置线程日志上下文");
        try {
            LOGGER.debug("调用函数");
            consumer.accept(t);
            LOGGER.debug("发起异步调用完成");
        } catch (Exception e) {
            LOGGER.warn(e, "发起异步调用异常");
        } finally {
            MDC.clear();
        }
    }
    private void dealLogContext(Map logContext) {
        MDC.clear();
        if (logContext == null) {
            logContext = new HashMap<>();
            logContext.put("traceId", Logger.genTraceId());
        }
        MDC.setContextMap(logContext);
    }

对于消息队列MQ,则需要通过传参的方式,在消费者端,手动给当前线程ThreadLocal赋值。

你可能感兴趣的:(java,java,jvm,开发语言)