解决线程池本地变量问题,TransmittableThreadLocal详解

ThreadLocal

ThreadLocal又叫本地线程变量,仅供当前线程使用。但是只能用于当前线程,无法传递变量到子线程中。

package com.fandf.demo.threadlocal;

/**
 * 父子线程之间传递变量
 *
 * @author fandongfeng
 * @date 2022/7/8 15:40
 */

public class ThreadLocalDemo {

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws Exception {
        threadLocal.set(12345);
        //主线程中生成一个子线程
        Thread thread = new MyThread();
        thread.start();
        System.out.println("主线程threadLocal = " + threadLocal.get());
        threadLocal.remove();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("子线程threadLocal = " + threadLocal.get());
        }
    }

}

运行结果

子线程threadLocal = null
主线程threadLocal = 12345

InheritableThreadLocal

实际工作中可能会出现 父线程创建几个子线程并发执行任务,那么父线程的本地变量如何传递到子线程呢? 答案是使用InheritableThreadLocal。

package com.fandf.demo.threadlocal;

/**
 * 父子线程之间传递变量
 *
 * @author fandongfeng
 * @date 2022/7/8 15:40
 */

public class InheritableThreadLocalDemo {

    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws Exception {
        threadLocal.set(12345);
        //主线程中生成一个子线程
        Thread thread = new MyThread();
        thread.start();
        System.out.println("主线程threadLocal = " + threadLocal.get());
        threadLocal.remove();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("子线程threadLocal = " + threadLocal.get());
        }
    }

}

运行结果

子线程threadLocal = 12345
主线程threadLocal = 12345

源码分析

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * 父线程创建子线程时,向子线程复制InheritableThreadLocal变量时使用
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */

    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * 获取Thread对象里的inheritableThreadLocals
     *
     * @param t the current thread
     */

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 将父线程的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);
    }
}

查看 java.lang.Thread类新建线程的方法

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
     /** 省略  **/
    //从父线程拷贝线程本地变量到子线程
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

}

查看ThreadLocal.createInheritedMap方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

//创建子线程时逐个读取父线程的本地变量,赋值给子线程本地变量
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++;
            }
        }
    }
}

线程池问题

  • • InheritableThreadLocal只有在创建的时候才拷贝,只拷贝一次,然后就放到线程中的inheritableThreadLocals属性缓存起来。由于使用了线程池,该线程可能会存活很久甚至一直存活,那么inheritableThreadLocals属性将不会看到父线程的本地变量的变化.

/**
 * 线程池环境下变量读取
 *
 * @author fandongfeng
 * @date 2022/7/8 16:25
 */

public class InheritableThreadLocalDemo2 {

    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    public static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开启");
        threadLocal.set(1);

        executorService.submit(() -> {
            System.out.println("子线程读取本地变量:" + threadLocal.get());
        });

        TimeUnit.SECONDS.sleep(1);

        threadLocal.set(2);

        executorService.submit(() -> {
            System.out.println("子线程读取本地变量:" + threadLocal.get());
        });
    }

}

运行结果

主线程开启
子线程读取本地变量:1
子线程读取本地变量:1

TransmittableThreadLocal

TransmittableThreadLocal就是为了解决线程池之间ThreadLocal本地变量传递的问题。

下面是几个典型场景例子。

  • • 分布式跟踪系统

  • • 日志收集记录系统上下文

  • • Session级Cache

  • • 应用容器或上层框架跨应用代码给下层SDK传递信息

/**
 * @author fandongfeng
 * @date 2022/7/8 16:31
 */

public class TransmittableThreadLocalDemo {

    public static ThreadLocal<Map<String, Object>> threadLocal = new TransmittableThreadLocal<>();
    public static ExecutorService executorService =
            TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开启");
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("name""fandf");
        threadLocal.set(map);
        System.out.println("主线程读取本地变量:" + threadLocal.get().get("name"));

        executorService.submit(() -> {
            System.out.println("子线程读取本地变量:" + threadLocal.get().get("name"));
        });

        TimeUnit.SECONDS.sleep(1);

        threadLocal.get().put("name""ffff");
        System.out.println("主线程读取本地变量:" + threadLocal.get().get("name"));

        executorService.submit(() -> {
            //[读到了主线程修改后的新值]
            System.out.println("子线程读取本地变量:" + threadLocal.get().get("name"));
            threadLocal.get().put("name""dddd");
            System.out.println("子线程读取本地变量:" + threadLocal.get().get("name"));
        });

        TimeUnit.SECONDS.sleep(1);
        System.out.println("主线程读取本地变量:" + threadLocal.get().get("name"));
    }

}

输出如下

主线程开启
主线程读取本地变量:fandf
子线程读取本地变量:fandf
主线程读取本地变量:ffff
子线程读取本地变量:ffff
子线程读取本地变量:dddd
主线程读取本地变量:dddd

代码地址:https://github.com/fandf/SpringCloudLearning/tree/master/fdf-demo/demo/src/main/java/com/fandf/demo/threadlocal

更多技术文章可以查看我的博客地址:http://fandf.top


原文始发于微信公众号(好好学技术):解决线程池本地变量问题,TransmittableThreadLocal详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/219676.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!