1.ThreadLocalMap内部结构

   static class ThreadLocalMap {/**  这里的Entry继承了弱引用,(弱引用只要发生GC就要被回收)*  Entry的key(ThreadLocal)以弱引用的方式指向ThreadLocal对象。*  (可以避免内存泄露)*  (参考 https://blog.csdn.net/qq_46312987/article/details/117166100)*/static class Entry extends WeakReference<ThreadLocal<?>> {Object value;         Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}//Entry数组初始长度,数组的长度必然是2的次方数private static final int INITIAL_CAPACITY = 16;//Entry数组private Entry[] table;//Entry数组中的元素个数private int size = 0;/** 扩容阈值 初始值为 len * 2/3* 触发后调用 rehash()方法* rehash() 方法先做一次全局检查过期数据,把散列表中所有过期的entry移除* 如果移除之后 当前散列表中的entry个数仍然达到 阈值的3/4,就进行扩容。*/private int threshold; //设置阈值为当前数组长度的2/3private void setThreshold(int len) {threshold = len * 2 / 3;}/** @param i 当前下标* @param len 数组长度* 返回当前位置的下一个位置*/private static int nextIndex(int i, int len) {/** 当前下标+1 如果小于数组长度的话 返回 +1的值* 如果等于数组长度的话,就返回0,即形成了环*/return ((i + 1 < len) ? i + 1 : 0);}/** 跟nextIndex()方法类似。返回当前位置的上一个位置*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/** 构造方法*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//构造一个初始长度为16的Entry数组table = new Entry[INITIAL_CAPACITY];//寻址,(hash值 & length - 1)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//构造一个Entry对象放到指定的位置上table[i] = new Entry(firstKey, firstValue);//size = 1  size = 1;//设置阈值为初始化容量(16)的2 / 3 = 10setThreshold(INITIAL_CAPACITY);}}

2.getEntry()方法详解

//----------------------------ThreadLocal.get()-------------------------------
// 此方法详解在 [https://blog.csdn.net/qq_46312987/article/details/121799343]
// 这里我们详细讲解 getEntry()方法。public T get() {//获取当前线程对象Thread t = Thread.currentThread();//获取当前线程内部的threadLcoalMapThreadLocalMap map = getMap(t);//内部的map不为NULL,if (map != null) {//这里调用了getEntry(),去threadLocalMap中查询指定的数据ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
//-----------------------------ThreadLocalMap.getEntry()---------------------/**  ThreadLocal对象的get()操作实际上是由ThreadLocalMap.getEntry()完成的。*/   private Entry getEntry(ThreadLocal<?> key) {//寻址int i = key.threadLocalHashCode & (table.length - 1);//获取指定位置的EntryEntry e = table[i];/** 当前位置不为NULL 并且当前位置的key(ThreadLocal对象)与传来的key是否一致* 注意 ThreadLocalMap与HashMap或者是ConcurrentHashMap最大的区别就是* 当出现hash冲突时使用的是开放寻址法(找下一个位置)而不是拉链法(在当前位置形成             * 链表)*/if (e != null && e.get() == key)return e;else//在当前位置没找到就去后面继续找。return getEntryAfterMiss(key, i, e);}//-----------------------ThreadLocalMap.getEntryAfterMiss()-------------------/** @param key   ThreadLocal对象* @param i     第一次寻址的索引位置* @param e     table第i个位置的entry对象* * 此方法的作用就是从当前位置向后查询,查询到指定数据返回,当查询到某一个位置的             * Entry为NULL时结束,最终返回NULL,在查询过程中如果某一个位置的entry不为NULL,* 但是key为NULL,说明对应的当前entry关联的ThreadLcoal对象已经被回收了,那么就       * 会将当前的entry清理掉*/      private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;//向后查找如果还没有查找到 但是当前位置的entry为NUll,说明查找不到,结束循环while (e != null) {//entry继承了弱引用,get()方法就是获取内部的ThreadLocal对象ThreadLocal<?> k = e.get();//查找成功,返回entry。if (k == key)return e;/* key是NULL,说明entry关联的ThreadLocal被回收了,但是entry还存在,* 这时就需要将当前位置的entry干掉。*/if (k == null)expungeStaleEntry(i);//k不为NULL,但是当前entry不是目标entry,继续向后查找elsei = nextIndex(i, len);e = tab[i];}return null;}//--------------------------ThreadLocalMap.expungeStaleEntry()----------------/** @param staleSlot (stale翻译为过期的) 此方法的作用* 1.即当前位置上的entry的key是NULL,说明当前的entry已经没有用了。* 需要将其干掉。* 2.遍历哈希表,将从当前位置开始的entry != null && key == null的所有entry         *   干掉,然后将正常的entry做一次重新迁移,优化查询。           */private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;//将当前位置的entry的的value置为NULL(help GC)tab[staleSlot].value = null;//将当前位置置为NULL,将entry直接干掉tab[staleSlot] = null;//size - 1size--;//下面就是rehash()的过程//当前遍历的entryEntry e; //当前的位置int i;//循环遍历数组(从置为NULL的entry对象的下一个位置开始),直到某一位置上的entry为NULL为止。for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {/** 能进入for循环,说明当前位置的entry一定不为NULL。*/ThreadLocal<?> k = e.get();//k(ThreadLocal对象) = NULL,将当前entry的value和当前entry全部干掉。if (k == null) {e.value = null;tab[i] = null;size --;} else {/**  执行到这里,说明当前位置的entry不为NULL。*  此时需要执行的事就是判断当前位置上的entry是否在经过哈希寻址后应                      *  该在的位置,(因为有可能发生过冲突),如果不在该在的位置,就去寻找*  距离寻址位置最近的位置(也可能找到寻址的位置)。*///重新计算索引int h = k.threadLocalHashCode & (len - 1);//条件成立 表示当前entry确实不在该在的位置,需要尝试重新找位置存放。if (h != i) {tab[i] = null;//循环找位置存放while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}

3.getEntry()运行流程图

4.set()方法详解

//-----------------ThreadLocalMap.set()---------------------------------------private void set(ThreadLocal<?> key, Object value) {//寻址Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);/**  for循环做的事就是,循环寻找key相同的entry。*  1.找到相同key并且正常的entry,做value替换*  2.找到某一位置(entry != null && entry.key == NULL),将entry替换。*/for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//找到了相同的key,替换value。if (k == key) {e.value = value;return;}//查找过程中,碰到了entry.key == null,说明当前entry是过期数据if (k == null) {//替换过期的entry。replaceStaleEntry(key, value, i);return;}}/** 执行到这里,说明for循环找到了一个当前slot为NULL的情况,* 此时直接在这个slot位置上创建一个Entry对象。*/tab[i] = new Entry(key, value);int sz = ++size;/** 这里做一次清理工作,cleanSomeSlots()返回true表示内部没有清理到数据* 这时在判断元素数量是否达到了扩容阈值,大于等于阈值就进行rehash()操作*/if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}//----------------------ThreadLocalMap.replaceStaleEntry----------------------/** 替换过期的entry。*  @param key 新key*  @param value 新value*  @param staleSlot 过期entry的位置*/private void replaceStaleEntryreplaceStaleEntry
(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;//将过期entry的位置赋值给slotToExpungeint slotToExpunge = staleSlot;/** 以当前staleSlot位置的前一个位置开始,向前迭代查找,* (结束条件entry = null),更新slotToExpunge为靠前的* (entry != null && entry.key == null) 的过期entry的位置。*/for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len)){if (e.get() == null){slotToExpunge = i;}}/** 从当前不合法的entry的位置的下一个位置开始遍历,* */for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {//获取当前位置的entry的key(threadLocal)ThreadLocal<?> k = e.get();/** key要添加的新key* k 当前遍历的key* k == key 说明要添加的key已经存在了,需要替换value* 然后做清理逻辑*/if (k == key) {//替换value。e.value = value; //将过期的entry放到当前位置i,因为下面要从i这个位置开始清理tab[i] = tab[staleSlot];//将替换完毕的entry放到过期数据的位置tab[staleSlot] = e;//条件成立:说明replaceStaleEntry一开始时向前查找过期数据时,并未                   //找到过期的entry.if (slotToExpunge == staleSlot)//因为上面做了交换,所以当前位置i就是过期数据,赋值给slotToExpungeslotToExpunge = i;//下面就是清理过期的entry。cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}/* * 条件1: k == null成立,说明当前遍历的entry是一个过期数据* 条件2: slotToExpunge == staleSlot成立说明一开始向前查找过期数据               * 并未找到过期的entry*/if (k == null && slotToExpunge == staleSlot)//因为向后查询过程中查找到了一个过期数据,更新slotToExpunge为当前位                   //置,前提条件是前驱扫描时未发现过期数据slotToExpunge = i;}/**  什么时候执行到这里?*—>向后查找过程中,并未发现 key = null 的entry,说明当前set操作是一个添加*  逻辑,直接将新数据添加到过期entry的位置上。*/tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);//条件成立:说明除了当前staleSlot过期entry位置以外,还发现其他的过期slot了if (slotToExpunge != staleSlot)//开启清理数据的逻辑。cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}//------------------ThreadLocalMap.cleanSomeSlots----------------------------/** @param i 表示清理工作的起始位置,这个位置一定是NULL。* @param n 表示table.length*/  private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {//获取当前i位置的下一个位置i = nextIndex(i, len);//获取位置上的entry。Entry e = tab[i];/** 条件成立表示当前位置的entry是过期数据,* 需要清理  */if (e != null && e.get() == null) {n = len; //清理表示置为true,表示清理过removed = true;//以当前过期的slot位置开始,做一次探测式清理工作。i = expungeStaleEntry(i);}/** 表示循环次数。*/                } while ( (n >>>= 1) != 0);return removed;}

5.set()方法流程图

6.rehash()方法

只有当前table元素个数大于等于扩容阈值并且在清理完table内部的所有过期的entry后,元素个数还大于等于阈值的3/4,这时才会触发扩容。

//--------------------ThreadLocalMap.rehash()---------------------------------private void rehash() {//这个方法执行完毕后,当前散列表内部所有过期的数据,都会被干掉。expungeStaleEntries();//条件成立,说明清理完所有的过期entry后,size数量仍然达到了扩容阈值的 3/4//才会去做一次resize()扩容。if (size >= threshold - threshold / 4)resize();}//------------------------ThreadLocalMap.resize()----------------------------- private void resize() {//扩容,变为原长度的2倍Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2; // 新长度 = 原长度 * 2;//创建一个新的table,Entry[] newTab = new Entry[newLen];//表示新表中的元素个数int count = 0;//遍历原表中的每一个slot,将原表中的数据迁移到新表。for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null; // Help the GC} else {//计算新位置int h = k.threadLocalHashCode & (newLen - 1);//遍历找空位置(找到距离目标位置最近的一个slot)while (newTab[h] != null)h = nextIndex(h, newLen);//放到新位置newTab[h] = e;count++;}}}setThreshold(newLen); //设置新的扩容阈值size = count; // 将count赋值给size table = newTab; //将新表赋值给table。}//----------------------ThreadLcoalMap.remove()-------------------------------private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;//根据key获取索引位置int i = key.threadLocalHashCode & (len - 1);//遍历for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {//找到指定keyif (e.get() == key) {                    /** entry是弱引用,调用clear()方法会将内部关联的threadLocal置为                  * NULL */e.clear();//清理当前位置 将entry内部的value以及entry干掉。expungeStaleEntry(i);return;}}}

7.三大清理方法流程图

expungeStaleEntry运行流程

cleanSomeSlots运行流程

replaceStaleEntry运行流程

ThreadLocal源码解析2.ThreadLocalMap相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 43 ThreadLocal 源码解析

    43 ThreadLocal 源码解析 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1 用法演示 ...

  2. JDK8 ThreadLocal 源码解析与最佳实践

    文章目录 用法 Example1 Example2 Springboot @Transcation 注解的原理 Entry 的 Key 设置为弱引用有什么好处 内存泄漏问题 This class pr ...

  3. ThreadLocal源码解读 侵立删

    转自:http://www.cnblogs.com/micrari/p/6790229.html 1. 背景 ThreadLocal源码解读,网上面早已经泛滥了,大多比较浅,甚至有的连基本原理都说的很 ...

  4. 深入解析ThreadLocal源码

    概念 ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问. 当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每 ...

  5. ThreadLocal与InheritableThreadLocal源码解析

    ThreadLocal 变量值的共享可以使用public static变量的形式实现,所有的线程都使用同一个public static变量,那如何实现每一个线程都有自己的变量呢?JDK提供的Threa ...

  6. Spring - 深入解析ThreadLocal源码

    概念 ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问. 当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每 ...

  7. HandlerThread和IntentService源码解析

    简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将这两者放在一起分析. HandlerThread: HandlerThread 其实是Handler ...

  8. Handler消息机制(九):IntentService源码解析

    作者:jtsky 链接:https://www.jianshu.com/p/0a150ec09a32 简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将 ...

  9. Handler 源码解析——Handler的创建

    前言 Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信.这是两个完全不同的概念. Handler先进先出原则,Looper类用来管理特定 ...

最新文章

  1. python写货币转换_如何在Python中将货币字符串转换为浮点数?
  2. MySQL查看表占用空间大小
  3. matlab读取黑白图目标位置的坐标,matlab对图像处理——裁剪 获取灰度图 获取坐标点...
  4. /* compiled code */ ?
  5. [CTSC2008]网络管理Network
  6. 发现一款好用的备份新浪博客的工具
  7. react+wabpack 搭建
  8. 如何解密单片机内程序?
  9. 设备日常检查(巡检)
  10. win10系统鼠标右键一直转圈的两种解决方法
  11. 中文版Ubuntu系统转为英文版Ubuntu
  12. PHP语法基础篇——除法取整和取余数
  13. 三维激光测距之三角法测距
  14. java 中session能存什么不能存什么
  15. c语言双边滤波算法,浅析bilateral filter双边滤波器的理解
  16. win10添加美式键盘_Windows10添加中文美式键盘,传统语言栏,采用ctrl+shift切换输入法...
  17. 推荐一波书单(多阅读,多思考,就会留住好奇心)
  18. UG打开stp格式模型,总是把以前的模型也一起打开
  19. 【 js基础 Day4】面向过程,面向对象,自定义对象,内置对象
  20. 用 Smali 手写一个可运行的 HelloWorld!!!

热门文章

  1. 直流电机控制 pwm 和 pid 算法
  2. 中段尾段全段什么意思_什么头什么尾?汽车改装排气时有的换全段,中尾段,尾段,分别都有什么效果?...
  3. 我的世界服务器圈地系统,我的世界领地指令大全 圈地方法分享
  4. 算法面试题(格灵深瞳)
  5. Note For Linux By Jes(12)-认识系统服务(daemons)
  6. linux的基本指令--第三节
  7. 单源最短路模板(转自 海子)
  8. 91461452469259f108dee0593bece4cb
  9. C#模仿腾讯QQ源码下载(附效果图)_张童瑶的博客
  10. 如何清除弹窗FF新推荐