ThreadLocal源码分析

一、实例代码

先来看一个使用ThreadLocal的实例,然后再开始我们的讲解

class Tools6 {

    public static ThreadLocal<Integer> intLocal = new ThreadLocal<>();
    public static ThreadLocal<String> strLocal = new ThreadLocal<>();

}

class ThreadA6 extends Thread {

    public void set() {
        Tools6.intLocal.set(123);
        Tools6.strLocal.set("luwenhe");
    }

    public void get() {
        Integer integer = Tools6.intLocal.get();
        String s = Tools6.strLocal.get();
        System.out.println("int: " + integer + " str: " + s);
    }

    @Override
    public void run() {
        System.out.println("------" + Thread.currentThread().getName() + "------");
        System.out.print("赋值前:");
        get();
        set();
        System.out.print("赋值后:");
        get();
    }
}

class ThreadB6 extends Thread {

    public void set() {
        Tools6.intLocal.set(456);
        Tools6.strLocal.set("luwenhe456");
    }

    public void get() {
        Integer integer = Tools6.intLocal.get();
        String s = Tools6.strLocal.get();
        System.out.println("int: " + integer + " str: " + s);
    }

    @Override
    public void run() {
        System.out.println("------" + Thread.currentThread().getName() + "------");
        System.out.print("赋值前:");
        get();
        set();
        System.out.print("赋值后:");
        get();
    }
}

public class Run6 {

    public static void main(String[] args) throws InterruptedException {
        ThreadA6 threadA6 = new ThreadA6();
        threadA6.setName("AAA");
        threadA6.start();

        Thread.sleep(2000);

        ThreadB6 threadB6 = new ThreadB6();
        threadB6.setName("BBB");
        threadB6.start();
    }

}

结果是:

------AAA------
赋值前:int: null str: null
赋值后:int: 123 str: luwenhe
------BBB------
赋值前:int: null str: null
赋值后:int: 456 str: luwenhe456

从结果可以很清晰的看到,即使在线程 AAA 中已经用 intLocal 和 strLocal 这两个对象赋了值了,但是当我们在线程 BBB 中还没赋值之前得到的两个值却是 null 的,说明在几个线程中,虽然各自线程都用同样的 ThreadLocal 对象对数据进行操作,但是 set 和 get 在每个线程中都是使用的,类似两张平行的桥,每个线程各走各的,不会有联系

JDK 的文档是这么说的:

This class provides thread-local variables.  These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable.  {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).

解释一下:该类提供线程局部变量。这些变量不同于它们的正常对应关系,即每一个线程都有它自己的、独立初始化的变量的拷贝。

ThreadLocal 提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。

简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。


二、具体实现

在 Thread 类中,有这么一个变量

public class Thread implements Runnable {
	...    
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}

该变量 inheritableThreadLocals 是由 ThreadLocal 类的内部类 ThreadLocalMap 创建的,先记住这个变量,然后再来看具体的 ThreadLocal 类

先来看 ThreadLocal 的 set() 方法吧

public void set(T value) {
    // 得到当前线程对象
    Thread t = Thread.currentThread();
    // 通过线程对象得到 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    // 如果 map 对象存在,就将当前 ThreadLocal 的对象作为 key,要存储的值作为 value 放到 map 的键值对中去
    if (map != null)
        map.set(this, value);
    //如果 map 对象不存在,就在当前线程中创建一共 ThreadLocalMap 类,传入的 key 是当前线程对象 t,value 是要存储的值
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    //通过当前线程的对象 t,来获取当前线程对应的 ThreadLocalMap 对象
    return t.threadLocals;
}

通过 getMap() 可以看到,该方法接受的是当前线程的对象 t,然后返回的是根据对象 t 来获取的 ThreadLocalMap 类,ThreadLocalMap 是 ThreadLocal 的内部类,说明每个线程都有各自的一个 ThreadLocalMap 内部类

先来看 createMap 方法

//创建 ThreadLocalMap 类的方法
void createMap(Thread t, T firstValue) {
    //this 为 ThreadLocal 对象,firstValue 为传入的值
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

该方法接受传入当前的线程对象 t 作为 key,和存储的值作为 value,然后用传入的线程对象 t 创建 ThreadLocalMap 类,ThreadLocalMap 的键是 ThreadLocal 的对象,值还是传入的值,说明每一个线程 Thread 都有自己的 ThreadLocalMap 类

ThreadLocalMap 类是 ThreadLocal 类的一共内部类,用 Entry 类来存储,该 Entry 类的 key 是当前线程的 ThreadLocal 对象,value 是传入的值

//ThreadLocal 类的内部类
static class ThreadLocalMap {
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        
        Object value;
		//key 是 ThreadLocal 对象,value 是传入的值
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    private final int threadLocalHashCode = nextHashCode();
    
    private static final int INITIAL_CAPACITY = 16;
    
    private Entry[] table;
    
    //实例化一个新的 ThreadLocalMap 对象
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //初始化一个大小为16的 Entry 数组
        table = new Entry[INITIAL_CAPACITY];
        //计算出传入的值在 Entry 数组中的位置
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //将传入的值放入到 Entry 数组的指定位置
        table[i] = new Entry(firstKey, firstValue);
        //默认的数组容量为 1
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

}

因为我们在每个 Thread 类中都有一个 inheritableThreadLocals 变量,该变量对应的是 ThreadLocal 的内部类 ThreadLocalMap,该内部类里面存放了一个 Entry 数组,Entry 数组用来保存一个 key-value 键值对,key 是 ThreadLocal 对象,每个 ThreadLocal 对象都有一个 threadLocalHashCode 值,每初始化一个 ThreadLocal 对象,该值就会增加一个固定大小 0x61c88647

在来看 ThreadLocalMap 的 set() 方法

private void set(ThreadLocal<?> key, Object value) {
    //将旧数组赋值给新的 Entry 数组
    Entry[] tab = table;
    int len = tab.length;
    //确定值插入的位置
    int i = key.threadLocalHashCode & (len-1);
	
    //遍历 Entry 数组,如果当前位置的 Entry 数组不为空
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        //得到当前位置 Entry 键值对的 key,即 ThreadLocal 对象
        ThreadLocal<?> k = e.get();
		
        //如果当前 Entry 的 key 和传入的 key 相同,则用的新的值替换旧的值
        if (k == key) {
            e.value = value;
            return;
        }
	
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
	
    //如果当前位置的 Entry 数组为空,则在当前位置新创建了一个 Entry 键值对
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

先根据 ThreadLocal 中的 threadLocalHashCode 变量的值,定位到 Entry 数组中的位置,过程如下:

  1. 如果当前位置的 Entry 数组为空,就新创建一个 Entry 对象放到位置 i 上,key 是当前线程中传入的 ThreadLocal 对象,value 是在当前线程中传入的值

  2. 如果当前位置的 Entry 数组不为空:

    2.1. 如果这个 Entry 对象的 ThreadLocal 对象和设置的 ThreadLocal 对象一样,那么就用新传入的值覆盖旧的值

    2.2. 位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置

img

可以看到,在同一个线程中,每创建一个 ThreadLocal 对象,就产生一个新的 Entry 节点,比如在上面的例子中,我们创建了两个 ThreadLocal 对象 intLocal 和 strLocal,当我们在线程 AAA 和线程 BBB 中使用以下语句时

//线程 AAA
Tools6.intLocal.set(123);
Tools6.strLocal.set("luwenhe");

//线程 BBB
Tools6.intLocal.set(456);
Tools6.strLocal.set("luwenhe456");

第一次在线程 AAA 中使用 set() 方法时,在 ThreadLocal 的 ThreadLocalMap 的 Entry 数组中,以 intLocal 对象为 key,123 为 value 创建一个位置为 3 的 Entry 对象,第二次,因为使用的是 strLocal 对象,因此在 Entry 数组中以 strLocal 为 key,luwenhe 为 value 创建一个位置为 10 的新的对象
当我们在线程 BBB 使用 set() 方法时,其实方法是一样的,不过这时是在线程 BBB 的 ThreadLocal 的 ThreadLocalMap 的 Entry 数组中,同时,两个 key 和线程 AAA 中是一样的,都是使用的 intLocal 和 strLocal

再来看 get() 方法

public T get() {
    //得到当前线程对象 t
    Thread t = Thread.currentThread();
    //通过线程对象 t 得到 ThreadLocalMap 对象 map
    ThreadLocalMap map = getMap(t);
    //如果 map 对象不为空
    if (map != null) {
        //根据当前线程的 ThreadLocal 对象,得到 ThreadLocalMap 中的 Entry 对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        //如果该 Entry 对象不为空,则直接得到 Entry 中的 value
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果 map 为空,使用 ThreadLocalMap 的初始值,初始 value 为 null
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    //根据 ThreadLocal 对象的 hash 值,定位到以该对象为 key 值的 Entry 对象在数组中的位置
    int i = key.threadLocalHashCode & (table.length - 1);
   	//得到具体位置的 Entry 对象
    Entry e = table[i];
    //如果该位置的 Entry 对象的 key 和 get 到的 key 一致,则直接返回这个 Entry 对象
    if (e != null && e.get() == key)
        return e;
    //否则,继续判断下一个位置
    else
        return getEntryAfterMiss(key, i, e);
}

二、参考

https://segmentfault.com/a/1190000014152795
http://www.cnblogs.com/dolphin0520/p/3920407.html
https://juejin.im/post/5a64a581f265da3e3b7aa02d

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值