ThreadLocal的实现原理

ThreadLocal是什么

ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。

ThreadLocal内存结构图

请添加图片描述
由内存结构图可以看出:

  • 每一个Thread对象中都持有一个ThreadLocalMap的成员变量。
  • ThreadLocalMap内部维护了一个Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。

以上两点在源码中可以更清晰地看出来。

ThreadLocal的重要属性

在源码中,ThreadLocal有以下重要属性:

	//当前ThreadLocal的HashCode,由下面的nextHashCode()计算得到,用于计算当前ThreadLocal在ThreadLocalMap中的索引位置。
 	private final int threadLocalHashCode = nextHashCode();
	
  	//保证每个ThreadLocal的ThreadLocalHashCode是唯一的
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    //哈希魔数
    private static final int HASH_INCREMENT = 0x61c88647;

 	//用于返回计算出的下一个哈希值
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

其中的哈希魔数HASH_INCREMENT转化为十进制是 1640531527,2654435769 转换成 int 类型就是 -1640531527,2654435769 等于 (√5-1)/2 乘以 2 的 32 次方。(√5-1)/2 就是黄金分割数,近似为 0.618。
也就是说 0x61c88647 可以理解为一个黄金分割数乘以 2 的 32 次方,它可以保证 nextHashCode 生成的哈希值,均匀的分布在 2 的幂次方上,且小于 2 的 32 次方

ThreadLocal还有一个重要属性,就是ThreadLocalMap。

ThreadLocalMap

ThreadLocalMap是在ThreadLocal中的一个静态内部类,部分源码如下:

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            //当前线程关联的value
            Object value;

			//创建键值对,键为ThreadLocal的引用
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        //初始容量为16,必须为2的幂
        private static final int INITIAL_CAPACITY = 16;

        //键值对的实体数组,长度必须为2的幂
        private Entry[] table;

        //ThreadLocalMap的长度(元素个数)
        private int size = 0;

        //扩容的阈值,默认为数组大小的三分之二
        private int threshold;

源码中有一个静态内部类Entry。Entry继承自WeakReference即设置为弱引用,如果设置为强引用,即使把ThreadLocal设置为null,GC也不会将其回收。

通过源码可以看出,ThreadLocalMap实际上是一个底层为数组的简单Map结构每个数据用Entry保存。Entry的键是ThreadLocal的引用,值就是ThreadLocal的值。

ThreadLocal的set()方法

源码如下:

 	public void set(T value) {
 		//获取当前线程
        Thread t = Thread.currentThread();
		
		//当前线程为Key,找到对应的ThreadLocalMap
        ThreadLocalMap map = getMap(t);

		//若map不为null则直接添加到本地变量,Key为当前ThreadLocal,值为添加的值
		//若map为null则先创建对应的map
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

先获取出当前的线程t,使用getMap()方法获取当前线程t对应的map。其中getMap()方法源码如下:

	ThreadLocalMap getMap(Thread t) {
		//返回当前的threadLocals
        return t.threadLocals;
    }

然后判断getMap()方法调用后的map不为null,就直接将值设置进map中;如果为null,则调用createMap()方法创建一个新map。createMap()方法源码如下:

 	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的get()方法

源码如下:

	public T get() {
		//获取当前线程
        Thread t = Thread.currentThread();
		
		//当前线程为Key,找到对应的ThreadLocalMap
        ThreadLocalMap map = getMap(t);

		//如果map不为空,就通过map找到对应Entry,再通过Entry的Key找到value并返回
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
		
		//若map为空,则初始化
        return setInitialValue();
    }

先获取出当前的线程t,使用getMap()方法获取当前线程t对应的map。如果map存在,则获取当前ThreadLocal对应的value值;如果map不存在,则调用setInitialValue()方法进行初始化。setInitialValue()源码如下:

    private T setInitialValue() {
		//initialValue()返回null,即将value置null
        T value = initialValue();

		//获取当前线程
        Thread t = Thread.currentThread();

		//当前线程为Key,找到对应的ThreadLocalMap
        ThreadLocalMap map = getMap(t);

		//若map不为null则直接添加到本地变量,Key为当前ThreadLocal,值为添加的值
		//若map为null则先创建对应的map
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        return value;
    }

ThreadLocal的remove()方法

源码如下:

 	 public void remove() {
		 //获取当前线程的ThreadLocalMap
         ThreadLocalMap m = getMap(Thread.currentThread());

		 //若map存在,则调用ThreadLocalMap的remove()方法
         if (m != null) {
             m.remove(this);
         }
     }

先获取出当前的线程对应的map,然后调用ThreadLocalMap的remove()方法对map中的元素进行删除。

ThreadLocal内存泄漏

ThreadLocal在没有外部强引用时,发生GC时会被回收,那么ThreadLocalMap中保存的key值就变成了null,而Entry又被ThreadLocalMap对象引用,ThreadLocalMap 对象又被 Thread 对象所引用,那么当Thread一直不终结的话,value对象就会一直存在于内存中,也就导致了内存泄漏,直至Thread被销毁后,才会被回收。

在使用完ThreadLocal变量后,需要我们手动remove掉,防止ThreadLocalMap中Entry一直保持对value的强引用,导致value不能被回收,进而避免内存泄漏。

总结

  • 每个线程都有一个属于自己的ThreadLocalMap。
  • ThreadLocalMlap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
  • 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

参考文献

https://baijiahao.baidu.com/s?id=1663127810801876375&wfr=spider&for=pc

https://www.cnblogs.com/fsmly/p/11020641.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值