threadlocal原理和线程间传递

前言

多线程中,除了加锁可以解决共享变量的不一致情况,还有就是使用threadlocal进行隔离。前者有锁等待的消耗,后者利用空间换取时间,每个线程中持有一份变量副本进行隔离。常见的日志中MDC是采用threadloacl实现的。

一.threadlocal原理

threadlocal引用关系如下图:

 其中,使用完threadlocal需要调用remove方法进行清空,否则会有内存泄漏风险。设计者也做了一些防范措施,在ThreadLocalget,set,remove方法,都会清除线程ThreadLocalMap里所有keynullvalue

二.线程间传递

1.new Thread直接创建线程

不复用的情况下,InheritableThreadLocal可以将父线程的内容传递到子线程

 

 

2.使用线程池

线程池会复用线程,InheritableThreadLocal只有在首次创建线程的时候会将父线程的内容传递到子线程,复用的时候,保存的是刚创建子线程时父线程的内容,此时无法获取到最新父线程的内容。

1.直接包装Runnable
void runOnCurrentThread() {

    // 从当前线程上获取MDC的所有数据

    Map tags = MDC.getCopyOfContextMap();

    Runable task = () -> {

        // 记录当前线程原有的数据,任务执行结束后再回复这些参数

        Map local = MDC.getCopyOfContextMap();

        // 在新线程上将上一个线程的MDC设置进去

        MDC.setContextMap(tags);

        try {

            runOnNextThread();

        } finally {

            // 恢复当前线程上的MDC

            MDC.setContextMap(local);

        }

    }

    // 在当前线程上,把task提交到线程池,task则会在threadpool中的某个线程上执行

    threadpool.execute(task);

}



void runOnNextThread() {

    // 跑在第二个线程上

    // do sth

}

2.实现线程池的execute
ExecuterService threadpool = new new ThreadPoolExecutor(...省略) {

    @Override

    public void execute(Runnable command) {

        // 从当前线程上获取MDC的所有数据

        Map tags = MDC.getCopyOfContextMap();

        Runable wrap = () -> {

            // 记录当前线程原有的数据,任务执行结束后再回复这些参数

            Map local = MDC.getCopyOfContextMap();

            // 在新线程上将上一个线程的MDC设置进去

            MDC.setContextMap(tags);

            try {

                // 执行真正的任务

                command.run();

            } finally {

                // 恢复当前线程上的MDC

                MDC.setContextMap(local);

            }

        }

        super.execute(wrap);

    }

}



// 下面这部分代码与第一版一模一样

void runOnCurrentThread() {

    Runable task = () -> runOnNextThread();

    // 在当前线程上,把task提交到线程池,task则会在threadpool中的某个线程上执行

    threadpool.execute(task);

}



void runOnNextThread() {

    // 跑在第二个线程上

    // do sth

}

3.使用ali的transmittable-thread-local

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

        // =====================================================

        // 在父线程中设置
        context.set("value-set-in-parent");

        Runnable task = () -> {
            // 在子线程中读取
            String value = context.get();
            println("task get context value: " + value);
        };

        // 额外的处理,生成修饰了的对象ttlRunnable
        Runnable ttlRunnable = TtlRunnable.get(task);
        executorService.submit(ttlRunnable);

        context.set("value-set-in-parent-2");
        ttlRunnable = TtlRunnable.get(task);
        executorService.submit(ttlRunnable);

        // =====================================================

        Thread.sleep(1000);
        executorService.shutdown();

注意:在子线程中,需要先保存当前线程的threadlocal存储的值,操作完成后,在恢复原来的值。父子线程是两个线程,是没有问题的,如果是以下情况,必须要恢复原始值


1.上面提到的场景,线程池满了 且 线程池使用的是『CallerRunsPolicy』,
则 提交到线程池的任务 在capture线程直接执行,也就是 直接在业务线程中同步执行;


2.使用ForkJoinPool(包含并行执行Stream与CompletableFuture,底层使用ForkJoinPool)的场景,展开的ForkJoinTask会在调用线程中直接执行。

总结:

1.threadlocal的原理以及防止内存泄漏

2.父子线程之间的threadlocal存储变量的传递

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值