ThreadLocal原理(二)

ThreaddLocal源码方法不是很多,主要有get()方法,set(T value)方法,remove()方法,initialValue()方法.

set(T value)方法

set方法用于设置线程本地变量的值.源码如下.

public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //判断这个map是否存在.
        if (map != null) {
        //将需要设置的值,放进map.
            map.set(this, value);
        } else {
            //创建一个map并且赋值.
            createMap(t, value);
        }
    }
getMap()方法如下,从线程里获取对应的map.
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
createMap()方法如下,创建了一个ThreadLocalMap赋值.
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
set(T value)方法流程

1:获取当前线程,然后获得当前线程ThreadLocalMap成员.赋值给map.

2:如果map不为空,将值放入map中.然后将当前ThreadLcoal作为key存入.

3:如果map为空的话,然后为该线程创建一个ThreadLocalMap.key为当前的ThreadLcoal.value为set方法的参数value.

get()方法

get方法用于获取当前线程变量的值.源码如下.
public T get() {
        //获取当前线程.
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLcoalMap.
        ThreadLocalMap map = getMap(t);
        //如果map不为null的话,就以当前的threadLocal作为key取出value.
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果为空的话,就会返回初始值.
        return setInitialValue();
    }

 setInitialValue()方法如下,设置初始值并返回.

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
get()方法流程

1:获取当前线程,然后获得当前线程ThreadLocalMap成员.赋值给map.

2:如果map不为空,以当前的ThreadLocal为key获取对应的value.

3:如果value不为空的话就返回当前的值.

4:如果map为null或者entry为空就会通过调用initialValue()方法初始化钩子函数获取初始值.并设置在map中.

initialValue()方法

当前线程本地变量没有绑定值的话,这个方法用于获取初始值.源码如下.

 protected T initialValue() {
        return null;
    }
如果没有调用set方法,直接调用get方法,就会调用这个方法,但是该方法只会被调用一次.默认情况下这个方法返回为null,如果不想返回null,可以继承ThreadLcoal覆盖这个方法.
public ThreadLocal<Foo> fooThreadLocal = 
ThreadLocal.withInitial(() -> new Foo());

 ThreadLocal.withInitial()静态方法源码如下.

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

 SuppliedThreadLocal类是一个静态内部类.源码如下.

//继承了ThreadLocal重写了initialValue方法
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        //保存钩子函数.
        private final Supplier<? extends T> supplier;
        //传入钩子函数.
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            //返回钩子函数的值作为初始值
            return supplier.get();
        }
    }

ThreadLocalMap源码 

ThreadLocal的操作都是基于ThreadLcoalMap展开的,而ThreadLocalMap是ThreadLocal的一个内部类.
ThreadLocalMap的主要成员变量
        //容量.
        private static final int INITIAL_CAPACITY = 16;

        //条目数组,作为哈希表使用.
        private Entry[] table;

        //map的条目数量
        private int size = 0;

        //扩容因子.
        private int threshold; // Default to 0


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

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

ThreadLocal中的get set remove方法都涉及到了ThreadLocalMap方法的调用.调用了如下方法,

1:set(ThreadLocal<?> key, Object value)

 向Map实例设置了key-value对.

2:getEntry(ThreadLocal<?> key)

从map实例获取所属的Entry.

3:remove(ThreadLocal<?> key)

根据key从map中移除实例所属的Entry.

ThreadLocalMap的set方法的分析.代码如下
  private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            //根据ket的hashcode值,获取对应的哈希表索引.
            int i = key.threadLocalHashCode & (len-1);

            //从槽点i位置开始向后循环搜索空的槽点,找空余槽点或者现有槽点.
            //如果没有现有槽点,必定有空余槽点,因为会扩容.
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //找到现有槽点,判断key是否和当前ThreadLcoal实例相等.
                if (k == key) {
                    e.value = value;
                    return;
                }
                //找到异常槽点,直接GC进行回收,重新设置值.

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            //如果没有发现新的槽点,增加新的Entry.
            tab[i] = new Entry(key, value);
            //设置ThreadLocal数量.
            int sz = ++size;
            //清理key为null的无效Entry.根据现在的数量和扩容因子判断是否要进行扩容.
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

好多东西都是纸上得来终觉浅,绝知此事要躬行.还是要多点点看看.多去理解.一遍不懂,那就多点几遍.

Entry的key要使用弱引用 

Entry用于保存ThreadLocalMap的key-value的条目.key使用了对ThreadLocal进行弱引用包装的实例作为key.代码如下.

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
为什么key要用弱引用呢.当一个线程被回收的话,对应的成员变量对ThreadLcoal引用也会被回收.但是ThreadLcoalMap里,存的是对ThreadLcoal的强引用.就会导致线程虽然被回收了,但是ThreadLocal还是没法回收,就会导致本地变量的值造成内存的泄漏.弱引用呢,仅有弱引用指向的对象只能过存活到下一次垃圾回收之前.换句话说,当GC发生时无论内存够不够,仅有弱引用指向的对象都会被回收,而拥有强引用的对象则不能被回收.

请抬头看看星空,有星星再看着你.

如果大家喜欢我的分享话,可以关注下微信公众号.

心有九月星辰.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值