ThreadLocal的源码解读

ThreadLocal的解读

基本测试使用代码

public class ThreadLoaclDemo {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static String test = "hello";
    public static void main(String[] args) {
        new Thread(() ->{
            threadLocal.set("hello threadLocal1");
            test += "1";
            System.out.println(threadLocal.get());
            System.out.println(test);
        }).start();

        new Thread(() ->{
            threadLocal.set("hello threadLocal2");
            test += "2";
            //threadLocal.remove(); 清除当前线程的本地内存中的 localVariable
            System.out.println(threadLocal.get());
            System.out.println(test);
        }).start();
    }
}

我们通常引出 ThradLocal,是考虑了这样的场景

在多线程处理共享变量时,都会存在着安全问题,,为了保证安全,通常会进行适当的同步操作

而同步的一般措施是:加锁

锁在保证着安全的同时,也会存在相关的效率和并发问题

那有没这样的变量,线程拿到后都是使用的自己的呢?

顺着这样的想法,很容易让人觉得,我们使用ThreadLocal,那么线程的本地变量就是存放在 ThreadLocal实例中,每个线程访问自己的变量,就是去 ThreadLocal通过当前线程实例拿到数据。

其实不是

线程的本地变量是存放在当前线程(调用线程)的 threadlocals

通过源码,我们发现,Thread 类中有两个变量

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

默认情况下是为 null的,但是会在线程第一次调用 ThradLocal类的set或者get方法时,才会创建

先分析 setgetremove方法

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 拿到当前用户线程的 threadLocalMap threadLocals
    ThreadLocalMap map = getMap(t);
    // 如果 threadLocals 不为空
    if (map != null) {
        // 从threadLocals中拿到当前线程本地变量中的值
        // key 是 当前 ThreadLocal 实例
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // threadLocals 为空,则初始化当前线程的threadLocals成员变量
    return setInitialValue();
}

private T setInitialValue() {
    // 初始化为 null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    return value;
}

通过源码发现,首先是获取当前线程,在用当前线程去调用getMap方法,去获取当前线程自己的变量(threadlocal),其被绑定到了线程的成员变量上

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

再看set,源码也类似

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 424 行
        // 将值存入 threadlocals 中
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

remove方法就是将 当前线程的 threadlocals 中的 ThreadLocal实例移除

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

通过这三个方法可以知道,其实 ThreadLocal 充当着的是一个工具类的作用

其并没有去过多的操控本类中的元素

而是变相的去使用当前线程中的 threadlocals

他调用 set 方法,将value 存入 调用线程的threadlocals中,调用 get方法取出来

如果调用线程一直不终止,那么存在threadlocals中的变量一直不会移除,这样就会有内存溢出的可能,所以适用后需要调用 ThreadLocal中的remove方法删除对应线程的 threadlocals中的本地变量

为何是map结构呢?

因为每个线程不止一个 ThreadLocal 变量

ThreadLocal也有一个短板,那就是 父子线程中无法使用 ThreadLocal共享

觉得这也不是 ThreadLocal 的短板,因为ThreadLocal,操作都是建立在当前线程

那么 InheritableThreadLocal因运而生

源码中就只有三个方法,也就是 重写的ThreadLocal的三个方法

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    /**
     * 获取也是 inheritableThreadLocals
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    /**
     * 重写 createMap 方法,创建的不再是 threadlocals 而是 inheritableThreadLocals
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

要想了解 InheritableThreadLocal 是如何让子线程访问父线程变量时,需要从Thread 的构造方法开始

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

追踪

/** 使用当前AccessControlContext初始化线程。*/
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

追踪

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    // 获取当前线程
    Thread parent = currentThread();
	/* 部分代码省略 */
    /*开始进入父子线程部分*/
    if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
        // 如果父线程的 inheritableThreadLocals 不为空
        // 那就设置子线程中的 inheritableThreadLocals 变量
        // 使用父线程的 inheritableThreadLocals 作为构造函数,创建一个新的 ThreadLocalMap 变量,赋值给子线程的 inheritableThreadLocals
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    this.stackSize = stackSize;
    tid = nextThreadID();
}

追踪 createInheritedMap

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

再进ThreadLocalMap看看是怎么通过父线程的ThreadLocalMap构造的

/**
* 构造一个新映射,其中包含来自给定父映射的所有可继承ThreadLocals
* 仅由createInheritedMap调用
*/
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) {
                // 这里就是调用 InheritableThreadLocal 重写的childValue方法
                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++;
            }
        }
    }
}

这下就知道了

当父线程创建子线程时,构造函数会把父线程中的 inheritableThreadLocals 的本地变量复制一份,保存在子线程中的 inheritableThreadLocals

inheritableThreadLocals 的使用场景

子线程需要使用父线程中存放的用户登录信息

中间件需要把统一的id追踪的整个调用链路记录

《java并发编程之美》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值