源码分析--ThreadLocal(二)

常用方法

(1).set方法:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

set方法的代码非常短,首先我们是获取的是当前的线程,然后调用getMap方法来获得TreadLocalMap对象,再来看看getMap方法获取的是什么:

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

getMap很简单,就是返回线程中ThreadLocalMap,跳到Thread源码里看,ThreadLocalMap是这么定义的:

ThreadLocal.ThreadLocalMap threadLocals = null;

后续的get方法也是会用到ThreadLocalMap,后续一起研究。

(2).get方法:
在使用ThreadLocal类时,get方法也是不可避免的,通常我们调用get方法来获取在ThreadLocal里面保存的变量。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

get方法也是非常的简单,最终还是到了ThreadLocalMap里面去了。

ThreadLocalMap

可以参考上节内容:源码分析–ThreadLocal(一)

现在主要看方法:
ThreadLocalMap的set方法:

    private void set(ThreadLocal<?> key, Object value) {
        
        //首先获取当前保存的数据table数组
        Entry[] tab = table;
        //获取数组的长度
        int len = tab.length;
        //将需要保存的ThreadLocal进行哈希计算出索引
        int i = key.threadLocalHashCode & (len-1);
        
       
        //先看看这个for循环进入和跳出的条件是什么,进入for循环的条件是获取的e不为空,不为空后进入for循环判断取出的key
        //是否和当前的ThreadLocal是否相同如果相同,那么就重新赋值并返回。如果不相同判断下k值是否存在,如果不存在但是table[i]还有值
        //只有一种情况那就是这个table[i]保存的ThreadLocal被垃圾回收掉了,Entry是弱引用,当不存在使用时会被回收掉,那么就进行替换并赋值
        //跳出for循环的条件是获取的tab[i]为空值,如果为空那么直接就赋值就可以了,这个条件很重要,后面的操作都是因为这个条件才进行的
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            
            ThreadLocal<?> k = e.get();
            //取出的Entry有值,判断保存的ThreadLocal和将要保存的ThreadLocal是否相同
            //如果相同直接赋值返回
            if (k == key) {
                e.value = value;
                return;
            }
            //如果取出的Entry为空值进行赋值并返回
            //这个时候说明改table[i]可以重新使用,用新的key-value将其替换,并删除其他无效的entry
            if (k == null) {
                //key值替换成新的ThreadLocal值,下面会进行讲解
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        //跳出for循环,说明tab[i]为空值,赋值就可以了
        tab[i] = new Entry(key, value);
        //当前的table大小加1
        int sz = ++size;
        //cleanSomeSlots用于清除那些e.get()==null,也就是table[index] != null && table[index].get()==null
        //之前提到过,这种数据key关联的对象已经被回收,所以这个Entry(table[index])可以被置null。
        //如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行rehash()
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

这块理解起来较为复杂,可以参考https://zhuanlan.zhihu.com/p/266525527

set方法的目的,我们都知道,就是将一个键值对成功保存在Entry数组里面,而保存的index不是指定,而是通过key的hashCode来计算的。将一个key的hashCode通过一个hash函数得到一个index值,而这个值就是这个键值对在数组里面的位置。

大致总结一下:

1.在set方法里面,将key的hashCode对len进行取模运算来获取index。这里需要注意的是len必须是2的n次方。
2.如果发生了哈希碰撞了,set方法采用的是开发地址方法来解决的。
3.在进行开放地址方法时,有可能会出现key被回收的情况,这里会可能会导致内存泄露的问题。官方的手段是,对key为null的Entry进行清理。

其他方法后续学习补充~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值