ThreadLocal及其扩展

ThreadLocal及其扩展

ThreadLocal是JDK提供的,是线程的本地变量,如果创建一个ThreadLocal变量,那么所有访问这个变量的线程都会有这个变量的一个本地副本。

ThreadLocal可以理解为是一个全局的初始变量,但是每个线程访问该变量的后续操作,都是对自身副本的操作,规避了线程安全问题。

一. ThreadLocal

1.Thread,ThrealLocal,ThrealLocalMap的关系?

在这里插入图片描述在这里插入图片描述
首先,每个Thread内部会维护一个threadLocals变量,这个变量的类型是ThrealLocal.ThreadLocalMap;而ThreadLocalMap是ThreadLocal的一个内部类;对ThreadLocal进行数据操作实际上是对ThreadLocalMap进行数据操作

2. API分析

​ 2.1 ThreadLocal.set(T value);

//1.  对ThreadLocal进行赋值操作    
public void set(T value) {
    // 获取当前操作该set()方法的线程,这也好理解,所有线程都是操作各自的ThreadLocal变量;
        Thread t = Thread.currentThread();  
    // 获取当前线程内部的ThreadLocalMap变量map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 如果该变量值不为空,则赋值,key为当前ThreadLocal自身;
            map.set(this, value);
        else
            // 如果变量值为空,则创建一个ThreadLocalMap变量
            createMap(t, value);
    }

//2. 创建ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
    // 如果线程没有map变量,则新建一个以当前ThreadLocal为key的一个map对象
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

由此可见,所有对ThreadLocal的操作,实际上都是对内部ThreadLocalMap的操作

​ 2.2 ThreadLocal.get()

// 1. 获取数据
public T get() {
    // 获取当前操作该get()方法的线程
        Thread t = Thread.currentThread();
    // 带着当前线程,调用getMap方法,返回该线程内部维护的map对象;
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                // 如果有值,则根据key获取结果并返回。
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
    	// 如果map值为空,则返回一个初始化值
        return setInitialValue();
    }

// 2. getMap(Thread t)方法
    ThreadLocalMap getMap(Thread t) {
        // 返回线程自身的map变量
        return t.threadLocals;
    }

// 3. setInitialValue()初始化方法
    private T setInitialValue() {
        T value = initialValue();// 默认获取null值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 返回当前线程自身的map变量
        if (map != null)
            map.set(this, value);	// 如果map变量不为空,则赋值
        else
            createMap(t, value);    // 如果map为空,则走createMap逻辑;
        return value;
    }

// 4.initialValue()方法,默认返回一个null值     
	protected T initialValue() {
        return null;
    }

​ 2.3 ThreadLocal.remove()

// 移除数据
public void remove() {
    // 获取当前线程内部的ThreadLocalMap类型的变量,如果不为空,则根据key移除数据;
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
3. 代码思考
public class Test {
    // 设置一个初始化的ThreadLocal对象,所有线程对它的操作都是操作各自的副本
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        // 主线程塞值
        threadLocal.set(1);
        System.out.println("操作主线程后的值:"+threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程取值
                System.out.println("子线程 s查询数据:"+threadLocal.get());
            }
        }).start();
    }
}

// 运行结果
操作主线程后的值:1
子线程查询数据:null

由此可见,每个线程对ThreadLocal的操作都是基于各自的副本,互不干扰,那么有些场景,父子线程需要进行数据传递(例如链路调用,下载中心异步调用等等),那么就需要在子线程中能拿到父线程的值,这个时候ThreadLocal就不适用,InheritableThreadLocal的出现,则是为了解决这个问题!

二. InheritableThreadLocal

InheritableThreadLocal继承自ThreadLocal,主要为了让子线程可以使用父线程中的本地变量。

  1. 先看效果
    在这里插入图片描述
    ​ 其他不变,单独改成InheritableThreadLocal,发现子线程中本地变量也能拿到父线程中的值。

  2. 看看原因
    在这里插入图片描述- 首先,Thread线程中包括两个变量,threadLocals与inheritableThreadLocals。

  • 看看线程的创建流程

    // 1. 线程的构造函数    
    public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
    
    // 2. 初始化方法
    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize) {
        //内部再调用一个init初始化方法,注意参数true
            init(g, target, name, stackSize, null, true);
        }
    
    // 3.内部init方法
    private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,
         boolean inheritThreadLocals) {
        // 获取父线程
            Thread parent = currentThread();
    	// 省略部分代码,看关键点,此处逻辑是,如果inheritThreadLocals为true(默认true),并且父线程中的	inheritableThreadLocals变量不为空,则将它赋值给当前线程的inheritableThreadLocals变量不为空变量
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        }
    
  • 到此,结合测试代码,就理解了。我们在主线程塞值,然后new一个线程,此时,上述流程中可得,子线程中的inheritableThreadLocals变量的值就等于主线程中塞的值,所以子线程操作inheritableThreadLocals变量时,能拿到主线程中的值,完成了父子线程中的数据传递。

    3. 再思考

    上述流程,我们可以发现,多线程中,只要使用InheritableThreadLocal,就可以完成父子线程数据传递,因为在new线程时,间接的完成了赋值操作,那么,如果类似线程池这种,用的是缓存线程,没有直接new线程时,如何完成数据传递呢?

三. TransmittableThreadLocal

TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnableTtlCallable 使用。— 此处引用TransmittableThreadLocal详解TransmittableThreadLocal使用及原理解析

后续继续补充。。。。。。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值