【Java】JDK源码分析——ThreadLocal




[]( )2\. nextHashCode方法

------------------------------------------------------------------------------------



用于产生ThreadLocal的哈希值。  

**ThreadLocal.java中的相关代码:**



private static int nextHashCode() {

	// 将nextHashCode的值加上HASH_INCREMENT的值,并返回

    return nextHashCode.getAndAdd(HASH_INCREMENT);

}



[]( )3.构造方法

------------------------------------------------------------------------



**ThreadLocal.java中的相关代码:**



public ThreadLocal() {

}



[]( )4\. initialValue方法

------------------------------------------------------------------------------------



用于为ThreadLocal提供当前线程的初始化值。  

默认为空,如果有需要,可以重写该方法。  

**ThreadLocal.java中的相关代码:**



protected T initialValue() {

    return null;

}



[]( )5\. getMap方法

------------------------------------------------------------------------------



获取指定线程的ThreadLocalMap。  

**ThreadLocal.java中的相关代码:**



ThreadLocalMap getMap(Thread t) {

	// 返回线程的全局变量

    return t.threadLocals;

}



[]( )6\. createMap方法

---------------------------------------------------------------------------------



为指定线程创建ThreadLocalMap用于保存线程间共享的对象,并存储firstValue。  

**ThreadLocal.java中的相关代码:**



void createMap(Thread t, T firstValue) {

	// 创建ThreadLocalMap对象

	// 保存到线程的全局变量threadLocals中

    t.threadLocals = new ThreadLocalMap(this, firstValue);

}



[]( )7\. createInheritedMap方法

------------------------------------------------------------------------------------------



该方法用于根据父线程的ThreadLocalMap对象创建子线程继承的ThreadLocalMap对象。  

**ThreadLocal.java中的相关代码:**



static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {

	// 调用构造方法,创建新对象

    return new ThreadLocalMap(parentMap);

}



[]( )8\. childValue方法

----------------------------------------------------------------------------------



该方法在InheritableThreadLocal类中被重写,用于根据父值而给出子值。  

该方法为了在必要时继承的子类可以不用重写ThreadLocalMap类,而通过此方法实现原本的功能。  

**ThreadLocal.java中的相关代码:**



T childValue(T parentValue) {

    throw new UnsupportedOperationException();

}



[]( )9\. remove方法

------------------------------------------------------------------------------



删除当前线程在ThreadLocal中存储的值。  

**ThreadLocal.java中的相关代码:**



public void remove() {

     // 获取当前线程的ThreadLocalMap对象

     ThreadLocalMap m = getMap(Thread.currentThread());

     // 若ThreadLocalMap不为空

     if (m != null)

         m.remove(this); // 移除当前线程存储的值

 }



[]( )10\. set方法

----------------------------------------------------------------------------



保存值到当前线程的ThreadLocalMap中。  

**ThreadLocal.java中的相关代码:**



public void set(T value) {

	// 获取当前线程

    Thread t = Thread.currentThread();

    // 获取当前线程的ThreadLocalMap对象

    ThreadLocalMap map = getMap(t);

    // 若ThreadLocalMap对象存在

    if (map != null)

        // 直接保存value

        map.set(this, value);

    else // 若ThreadLocalMap对象不存在

        // 则创建ThreadLocalMap对象,并保存value

        createMap(t, value);

}



[]( )11\. setInitialValue方法

----------------------------------------------------------------------------------------



用于将当前线程的ThreadLocalMap存储的值设置为初始值。  

**ThreadLocal.java中的相关代码:**



private T setInitialValue() {

	// 获取初始值

    T value = initialValue();

    // 获取当前线程

    Thread t = Thread.currentThread();

    // 获取当前线程的ThreadLocalMap对象

    ThreadLocalMap map = getMap(t);

	// 若ThreadLocalMap对象存在

    if (map != null)

        map.set(this, value); // 则保存值value

    else // 若ThreadLocalMap对象不存在

        createMap(t, value); // 则创建ThreadLocalMap对象,再保存值value

    // 返回初始值

    return value;

}



[]( )12\. get方法

----------------------------------------------------------------------------



获取当前线程的ThreadLocalMap中对应的值。  

**ThreadLocal.java中的相关代码:**



public T get() {

	// 获取当前线程

    Thread t = Thread.currentThread();

    // 获取当前线程的ThreadLocalMap对象

    ThreadLocalMap map = getMap(t);

    // 若ThreadLocalMap对象存在

    if (map != null) {

        // 尝试获取当前ThreadLocal对应的键值对

        ThreadLocalMap.Entry e = map.getEntry(this);

        // 若键值对存在,说明之前存储过

        if (e != null) {

            @SuppressWarnings("unchecked")

            // 获取键值对的值

            T result = (T)e.value;

            // 返回

            return result;

        }

    }

    // 若ThreadLocalMap对象不存在,

	// 则将ThreadLocal对应的值设置为初始值,并返回

    return setInitialValue();

}




[]( )13.ThreadLocal中的静态内部类ThreadLocalMap

-----------------------------------------------------------------------------------------------------



ThreadLocalMap是ThreadLocal用于存储键值对的内部类,ThreadLocal中对数据操作都是通过该内部类提供的方法实现的。  

**ThreadLocal.java中的相关代码:**



static class ThreadLocalMap {

}




### []( )1)全局变量



**ThreadLocal.java中的相关代码:**



	// 初始化容量,表示能够存储键值对的数量,必须是2的次方形式

    private static final int INITIAL_CAPACITY = 16;



    // 用于存储键值对的数组

    private Entry[] table;



    // 用于记录已经存储的键值对的数量

    private int size = 0;



    // 扩容的阈值,存储的键值对数量超过该值将会触发扩容,默认为0

    private int threshold;



### []( )2)ThreadLocalMap中的静态内部类Entry



该类用于存储键值对,其中键的类型为ThreadLocal,值的类型为Object。  

同时,该类继承了弱引用类WeakReference,当获取键为空时,说明该键的引用被回收。  

**ThreadLocal.java中的相关代码:**



	static class Entry extends WeakReference<ThreadLocal<?>> {

        // 线程中存储的值

        Object value;



        Entry(ThreadLocal<?> k, Object v) {

            super(k);

            value = v;

        }

    }



### []( )3)构造方法



#### []( )i)参数为键值对



该方法会创建ThreadLocalMap,并将参数中的键值对进行保存。  

**ThreadLocal.java中的相关代码:**



	ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

        // 根据初始化容量,创建键值对数组

        table = new Entry[INITIAL_CAPACITY];

        // 获取ThreadLocal的哈希值,和INITIAL_CAPACITY – 1进行与运算

        // 计算键值对在数组中的位置

        // INITIAL_CAPACITY – 1作为掩码

		// 如16-1=15,1111,最后进行与运算得到的数值一定在0到15之间

        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

        // 创建新的键值对,保存到数组对应的位置中

        table[i] = new Entry(firstKey, firstValue);

        // 记录当前键值对数量1

        size = 1;

        // 设置扩容阈值

        setThreshold(INITIAL_CAPACITY);

    }



#### []( )ii)参数为ThreadLocalMap



该方法用来根据继承的ThreadLocalMap的内容创建新的ThreadLocalMap。  

该方法是私有的,仅供createInheritedMap方法调用。  

**ThreadLocal.java中的相关代码:**



	private ThreadLocalMap(ThreadLocalMap parentMap) {

        // 获取继承的ThreadLocalMap中的键值对数组

        Entry[] parentTable = parentMap.table;

        // 获取数组长度

        int len = parentTable.length;

        // 根据长度设置扩容阈值

        setThreshold(len);

        // 根据长度创建新数组

        table = new Entry[len];



        // 循环,将键值对转移到新数组中

        for (int j = 0; j < len; j++) {

            // 获取键值对

            Entry e = parentTable[j];

            // 若j位置键值对存在

            if (e != null) {

                @SuppressWarnings("unchecked")

                // 获取键

                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();

                // 若键存在

                if (key != null) {

                    // 根据键,获取值

                    Object value = key.childValue(e.value);

                    // 根据键和值,创建新的键值对

                    Entry c = new Entry(key, value);

                    // 根据键ThreadLocal的哈希值

					// 计算出键值对在数组中的位置h

					// len - 1作为掩码

					// 如16-1=15,1111,

					// 最后进行与运算得到的数值一定在0到15之间

                    int h = key.threadLocalHashCode & (len - 1);

                    // 循环,若数组的h位置有键值对

                    // 则根据当前位置h和数组长度,获取下一个位置

                    // 直到找到一个没有键值对的位置

                    while (table[h] != null)

                        h = nextIndex(h, len);

                    // 将键值对存在对应位置

                    table[h] = c;

                    // 当前存储的键值对数量加1

                    size++;

                }

            }

        }

    }



### []( )4)setThreshold方法



用来设置扩容阈值。  

**ThreadLocal.java中的相关代码:**



	private void setThreshold(int len) {

        // 阈值为数组长度的三分之二

        threshold = len * 2 / 3;

    }



### []( )5)nextIndex方法



获取位置i的下一个位置。  

**ThreadLocal.java中的相关代码:**



	private static int nextIndex(int i, int len) {

        // 若i的下一个位置i+1小于数组长度len

        // 则返回i+1,否则返回0

        // 该操作相当于i对len取模

        return ((i + 1 < len) ? i + 1 : 0);

    }



### []( )6)prevIndex方法



获取位置i的上一个位置。  

**ThreadLocal.java中的相关代码:**



	private static int prevIndex(int i, int len) {

        // 若i的上一个位置i-1大于0

        // 则返回i-1,否则返回len-1

        return ((i - 1 >= 0) ? i - 1 : len - 1);

    }



### []( )7)expungeStaleEntry方法



删除指定位置的键值对,并对其它位置的键值对重新散列整理。  

**ThreadLocal.java中的相关代码:**



	private int expungeStaleEntry(int staleSlot) {

        // 获取数组

        Entry[] tab = table;

        // 获取数组的长度

        int len = tab.length;



        // 删除指定位置的键值对

        tab[staleSlot].value = null;

        tab[staleSlot] = null;

        // 存储的键值对数量减1

        size--;



        // 对数组循环整理,重新散列,

		// 直到遇到为空的位置,停止循环

        Entry e;

        int i;

        // 从staleSlot下一个位置i开始

        // 若i位置的键值对e为空,则跳出循环

        // 每次循环i向后移动一个位置,若移动到最后一个位置,则回到0开始

        for (i = nextIndex(staleSlot, len);

             (e = tab[i]) != null;

             i = nextIndex(i, len)) {

            // 从i位置的键值对e中获取键k

            ThreadLocal<?> k = e.get();

            // 若键为空

            if (k == null) {

                // 清除当前位置的键值对

                e.value = null;

                tab[i] = null;

                // 存储的键值对数量减1

                size--;

            } else { // 若键不为空

                // 重新计算键值对应该存放的位置h

                int h = k.threadLocalHashCode & (len - 1);

                // 若键值对应该存储的位置和当前存放的位置不一致

                if (h != i) {

                    // 对当前的位置进行清空

                    tab[i] = null;



                    // 循环,从键值对应该存放的位置h开始找

                    // 找到一个空位置,存放键值对

                    while (tab[h] != null)

                        h = nextIndex(h, len);

                    tab[h] = e;

                }

            }

        }

        // 返回跳出循环时键值对为空的位置

        return i;

    }



### []( )8)cleanSomeSlots方法



从位置i的下一位开始,向下查看log2(n)个位置,如果发现位置不对的键值对,则对其进行清理。  

**ThreadLocal.java中的相关代码:**



	private boolean cleanSomeSlots(int i, int n) {

        // 标志位,如果进行过清理,则为true

        boolean removed = false;

        // 获取键值对数组

        Entry[] tab = table;

        // 获取数组长度

        int len = tab.length;

        // 循环

        do {

            // 获取位置i的下一个位置,结果再赋给i

            i = nextIndex(i, len);

            // 获取该位置的键值对

            Entry e = tab[i];

            // 若该位置的键值对存在,并且键值对的键不存在

            if (e != null && e.get() == null) {

                // 重新设置循环次数

                n = len;

                // 设置标志位

                removed = true;

                // 删除指定位置的键值对,并进行整理

                i = expungeStaleEntry(i);

            }

        } while ( (n >>>= 1) != 0); // 循环log2(n)次

        // 返回结果

        return removed;

    }



### []( )9)expungeStaleEntries方法



删除数组中键不存在的键值对,对位置错误的键值对进行调整。  

**ThreadLocal.java中的相关代码:**



	private void expungeStaleEntries() {

        // 获取键值对数组

        Entry[] tab = table;

        // 获取数组长度

        int len = tab.length;

        // 循环遍历键值对数组

        for (int j = 0; j < len; j++) {

            // 获取键值对

            Entry e = tab[j];

            // 若键值对存在,并且键值对的键不存在

            if (e != null && e.get() == null)

                // 删除该位置的键值对,并整理。

                expungeStaleEntry(j);

        }

    }



### []( )10)resize方法



用于对键值对数组扩容,扩大之前容量的2倍。  

**ThreadLocal.java中的相关代码:**



	private void resize() {

        // 获取旧的键值对数组

        Entry[] oldTab = table;

        // 获取旧的键值对数组的长度

        int oldLen = oldTab.length;

        // 根据旧长度计算新长度,新长度为旧长度的2倍

        int newLen = oldLen * 2;

        // 根据新长度创建新数组

        Entry[] newTab = new Entry[newLen];

        // 用于记录新数组使用的空间数量

        int count = 0;



        // 循环遍历旧数组

        for (int j = 0; j < oldLen; ++j) {

            // 取出键值对

            Entry e = oldTab[j];

            // 若键值对存在

            if (e != null) {

                // 获取键值对的键

                ThreadLocal<?> k = e.get();

                // 若键不存在

                if (k == null) {

                    // 则将其值删除,即释放引用

                    e.value = null; // 可以帮助垃圾回收器快点回收

                } else { // 若键存在

                    // 重新计算其在新数组中的位置

                    int h = k.threadLocalHashCode & (newLen - 1);

                    // 若该位置已经被占了,则循环查找下一个可以存放的位置

                    while (newTab[h] != null)

                        h = nextIndex(h, newLen);

                    // 存放键值对到新数组中

                    newTab[h] = e;

                    // 新数组已经使用的空间数量加1

                    count++;

                }

            }

        }



        // 设置新的扩容阈值

        setThreshold(newLen);

        // 将局部变量保存到全局变量

        // 将已经使用的空间数量保存到全局变量

        size = count;

        // 将新数组保存到全局变量

        table = newTab;

    }



### []( )11)rehash方法



对所有的键值对进行整理,删除键不存在的键值对。  

如果整理后键值对的数量还是过高,则进行扩容。  

**ThreadLocal.java中的相关代码:**



	private void rehash() {

        // 整理所有键值对。

        expungeStaleEntries();



        // 当数组已经使用的空间数量超过阈值的四分之三

		// 即总空间的六分之一,则进行扩容

		// 使用更低的阈值避免滞后现象

        if (size >= threshold - threshold / 4)

            resize();

    }



### []( )12)replaceStaleEntry方法



**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/a23179f03ac36d5e58c45de69c429d46.jpeg)
![img](https://img-blog.csdnimg.cn/img_convert/346d2e3803a6eb518b25fbcdc501720a.png)
![img](https://img-blog.csdnimg.cn/img_convert/b2c18a031e1c3a76226913dcd2c575f2.png)
![img](https://img-blog.csdnimg.cn/img_convert/f735648dd938ff70b35b2c408a1e75e7.png)
![img](https://img-blog.csdnimg.cn/img_convert/d36ef5d30aa85edf6e85275857b5034e.png)
![img](https://img-blog.csdnimg.cn/img_convert/1e29fa2955475ce7de527fcf0fa772df.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)**
![img](https://img-blog.csdnimg.cn/img_convert/1c050b3f69e9cc5b96583080119c9771.jpeg)



## 最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

![image](https://img-blog.csdnimg.cn/img_convert/5b235d307ba59bc1091bea0a8822a431.webp?x-oss-process=image/format,png)

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/f88815777fcb1e4d550e8c562ffa8a0b.jpeg)

漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中...(img-GTG4Z4jG-1712880230524)]
[外链图片转存中...(img-x51Gzty2-1712880230525)]
[外链图片转存中...(img-8HoemXw5-1712880230525)]
[外链图片转存中...(img-fmYx4UdZ-1712880230526)]
[外链图片转存中...(img-uUilFJHC-1712880230526)]
[外链图片转存中...(img-6esaUk3a-1712880230526)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)**
[外链图片转存中...(img-SQdcb3EY-1712880230527)]



## 最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

[外链图片转存中...(img-69ftSlE5-1712880230527)]

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-LaPMcgeO-1712880230527)]

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值