ThreadLocal原理(二)

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

set(T value)方法

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

scss 复制代码
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.

javascript 复制代码
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

createMap()方法如下,创建了一个ThreadLocalMap赋值.

javascript 复制代码
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方法用于获取当前线程变量的值.源码如下.

java 复制代码
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()方法如下,设置初始值并返回.

ini 复制代码
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()方法

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

csharp 复制代码
 protected T initialValue() {
        return null;
    }

如果没有调用set方法,直接调用get方法,就会调用这个方法,但是该方法只会被调用一次.默认情况下这个方法返回为null,如果不想返回null,可以继承ThreadLcoal覆盖这个方法.

ini 复制代码
public ThreadLocal<Foo> fooThreadLocal = 
ThreadLocal.withInitial(() -> new Foo());

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

typescript 复制代码
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

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

scala 复制代码
//继承了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源码

erlang 复制代码
ThreadLocal的操作都是基于ThreadLcoalMap展开的,而ThreadLocalMap是ThreadLocal的一个内部类.
ThreadLocalMap的主要成员变量
scala 复制代码
        //容量.
        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方法的分析.代码如下
ini 复制代码
  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.代码如下.

scala 复制代码
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发生时无论内存够不够,仅有弱引用指向的对象都会被回收,而拥有强引用的对象则不能被回收.

流程图:

语雀地址www.yuque.com/itbosunmian...?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

如果大家喜欢我的分享的话,可以关注我的微信公众号 念何架构之路

相关推荐
小杍随笔2 小时前
【Rust 语言编程知识与应用:元编程详解】
开发语言·后端·rust
神奇小汤圆2 小时前
B+ 树的物理代价:当SQL慢了10毫秒,计算机底层发生了什么?
后端
小江的记录本2 小时前
【Java】Java核心关键字:final、static、volatile、synchronized、transient(附《面试高频考点》)
java·开发语言·spring boot·后端·sql·spring·面试
神奇小汤圆2 小时前
滴滴一面:在项目中使用多线程时遇到过哪些问题?
后端
羊小猪~~2 小时前
【QT】--QWIdget与QDialog
开发语言·数据库·c++·后端·qt·求职招聘
oyzz1202 小时前
SpringBoot最佳实践之 - 使用AOP记录操作日志
java·spring boot·后端
1104.北光c°2 小时前
Leetcode21.合并两个有序链表 双指针+递归 【hot100算法个人笔记】【java写法】
java·后端·程序人生·算法·leetcode·链表·学习方法
前端付豪2 小时前
实现代码块复制和会话搜索
前端·人工智能·后端
阿聪谈架构2 小时前
第06章:AI RAG 检索增强生成 — 从零到生产(上)
人工智能·后端