ThreadLocal源码学习

之前在看源码的时候,忽略了这个知识点,但是在后面看到框架底层源码的时候,看到spring的事务有用到这个点,所以就再回头学习下threadLocal的原理

首先threadLocal是线程本地变量的意思,主要用来做线程数据隔离的操作,在使用的过程中,我们主需要关注threadLocal就可以,但是在底层源码层面,需要设计到几个相关的知识:
thread、threadLocal、threadLocalMap三者之间的关系
1、thread对象持有了一个threadLocalMap,但是对threadLocalMap的set、get、remove都是通过threadLocal来进行操作的
2、threadLocalMap的key是threadLocal对象的指针,value是我们程序员要设置的值

弱引用

我们通常说threadLocalMap是弱引用,如何体现?

static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
    
    
        super(k);
        value = v;
    }
}

这是threadLocalMap的entry对象,可以看到,是继承了weakReference,是弱引用

set

在要做线程数据隔离的时候,通常我们调用的是threadLocal.set()方法,来看下源码

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * 这是设置当前线程绑定的局部变量
     * 1.获取到当前线程
     * 2.根据线程,获取到thread中所维护的threadLocalMap
     * 3.如果map不为null,就将当前threadLocal作为key,设置到线程维护的threadLocalMap中
     * 4.如果map为null,就创建一个新的threadLocalMap,并将key和value赋值进去
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

这里的getMap(t),就是调用thread中的threadLocalMap
set(this,value):调用的是threadLocalMap的set方法,关于map的set方法后面单独介绍

get

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     * 这里应该是根据当前线程从map中获取到当前线程对应的value
     */
    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()方法的时候,会根据当前线程从thread中获取到对应的threadLocalMap,我觉得这里就是为什么可以隔离的原因,不同的thread,获取到的是不同的threadLocalMap,如果当前线程对应的entry不为null,就返回对应的value

threadLocalMap.set()

接着来看下threadLocalMap的set方法是如何存入一个元素的

/**
 * Set the value associated with key.
 * 这里是向集合中插入元素的源码,这里和hashmap是不同的处理思想
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {
    
    

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    /**
     * 1.这里的threadLocalHashCode是根据atomicInteger来获取值的
     * 每调用一次set方法,atomicInteger ++;
     */
    int i = key.threadLocalHashCode & (len-1);

    /**
     * 2.判断i位置是否已经存在元素
     * 如果i != null,就通过nextIndex获取到i+1位置的元素,需要注意的是:如果 i+1 > len,就会从0开始
     */
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
    
    
        ThreadLocal<?> k = e.get();

        /**
         * 2.1 如果key相同,直接覆盖
         */
        if (k == key) {
    
    
            e.value = value;
            return;
        }

        /**
         * 2.2 如果i位置的key为null,就直接用当前key和value,写入到i位置
         * 这里就和弱引用有点关系了;
         * 如果key是弱引用,在使用完之后,如果我们没有调用threadLocal.remove()方法,key会被回收,因为key是弱引用
         * 但是value不会被回收,此时就出现了key为null,但是value依旧有值,如果是这种情况,在源码中就可以直接进行覆盖
         *
         * 但是反之,如果我们key使用的是强引用,即使使用完了,key也不会被回收,依旧不是null,在不是null的情况下,这里如何判断key是已经使用完了?还是没有使用完?
         * 所以:对于使用弱引用来说,即使我们没有调用remove方法,在一定程度上,可以通过key为null,value不为null这个场景来减少内存溢出的场景
         */
        if (k == null) {
    
    
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    /**
     * 3.cleanSomeSlots是清除那些e.get() == null的元素
     * 如果清除失败,且数组长度超过阈值,就扩容
     */
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

这里threadLocalMap在处理冲突的时候,采用的是开放寻址法,如果i位置冲突,就去i+1位置判断,是否可以放入到,如果i+1 大于数组长度,那就从0位置继续开始

threadLocalMap和hashmap

其实整个threadLocalMap的源码看下来,和hashmap是有很大的区别的
1.threadLocalMap的key是threadLocal对象
2.threadLocalMap的hash值的计算,是根据atomicInteger来计算的
3.在处理冲突的时候,threadLocalMap采用开放寻址法,hashmap采用链地址法

内存溢出问题

threadLocal如果使用不当会有内存溢出问题,既然threadLocal的key采用的是弱引用,为什么还会有内存溢出?因为key虽然是弱引用,但是value是强引用,在使用完了之后,key(threadLocal)会被回收,但是value依旧存在,所以会存在无法回收的场景
那如果map的key采用强引用,也会导致无法回收,但是如果使用弱引用,会有一个好处:
在往map中插入元素的时候,如果i位置的key不存在,但是value存在,那此时就会认为该位置可以进行覆盖,因为key为null,value不为null,就是被回收的场景,只是value是强引用,无法被回收
但是如果key和value都是强引用的话,那在set的时候,就无法区分key是使用完了,还是未使用完,所以:key使用弱引用,可以在一定程度避免内存溢出问题
所以,最好是,在使用完,就调用threadLocal的remove方法,将key和value从map中移除

总结

所以,总结而言:
1、对于threadLocal的使用,最好是在使用完了之后,就手动的remove
如果不remove,那由于threadLocalMap的特性,在使用完之后,会对key进行释放,在下次进行插入的时候,会对该位置进行覆盖,只是在一定程度上,可以避免内存溢出

2、threadLocalMap在冲突的时候,采用的是开放寻址法,key的hash值,是通过一个atomicInteger变量来计算的

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/114298525