发布于2021-05-30 20:01 阅读(615) 评论(0) 点赞(30) 收藏(4)
参考回答:
ThreadLocal 对象可以给每个线程分配一份属于自己的局部变量副本,多个线程之间可以互不干扰。一般我们会重写 initalValue()
方法来给当前 ThreadLocal 对象赋初始值。
参考回答:
threadLocals
(即ThreadLocalMap,它是一个Entry[]
数组,而不是 Map 集合哦~),各个线程在调用同一个 ThreadLocal 对象的set(value)
方法设置值的时候,就是往各自的 ThreadLocalMap 对象数组中新增值。Entry[]
数组)中存放的是一个个的 Entry
节点,它有两个属性字段,虚引用 key(ThreadLocal对象) ,和强引用 value (当前线程变量副本的值)。参考回答:
get()
方法时,方法内部会检测该线程的 ThreadLoacalMap 数组(Entry[]
)内是否存在 key
为当前 ThreadLocal 对象的 Entry
节点。如果数组内没有对应的节点,那么当前 ThreadLocal 对象就会调用其内部的 initialValue()
方法创建一个 Entry
节点存放到 ThreadLocalMap 中去。hashCode()
方法,而是通过自身的nextHashCode();
计算得来。代码如下:// threadLocalHashCode ---> 用于threadLocals的桶位寻址:
// 1.线程获取threadLocal.get()时:
// 如果是第一次在某个threadLocal对象上get,那么就会给当前线程分配一个value,
// 这个value 和 当前的threadLocal对象被包装成为一个 entry
// 其中entry的 key 是threadLocal对象,value 是threadLocal对象给当前线程生成的value
// 2.这个entry存放到当前线程 threadLocals 这个map的哪个桶位呢?
// 桶位寻址与当前 threadLocal对象的 threadLocalHashCode有关系:
// 使用 threadLocalHashCode & (table.length - 1) 计算结果得到的位置就是当前 entry 需要存放的位置。
private final int threadLocalHashCode = nextHashCode();
// nextHashCode: 表示hash值
// 创建ThreadLocal对象时会使用到该属性:
// 每创建一个threadLocal对象时,就会使用 nextHashCode 分配一个hash值给这个对象。
private static AtomicInteger nextHashCode = new AtomicInteger();
// HASH_INCREMENT: 表示hash值的增量~
// 每创建一个ThreadLocal对象,ThreadLocal.nextHashCode的值就会增长HASH_INCREMENT(0x61c88647)。
// 这个值很特殊,它是斐波那契数也叫黄金分割数。
// hash增量为这个数字,带来的好处就是hash分布非常均匀。
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 返回一个nextHashCode的hash值:
* 创建新的ThreadLocal对象时,使用这个方法,会给当前对象分配一个hash值。
*/
private static int nextHashCode() {
// 每创建一个对象,nextHashCode计算得到的hash值就增长HASH_INCREMENT(0x61c88647)
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/*
* 初始化一个起始value:
* 默认返回null,一般情况下都是需要重写这个方法的(例如第2小节的入门案例中就重写了该方法)。
*/
protected T initialValue() {
return null;
}
参考回答:
WeakReference<ThreadLocal<?>>
,而 HashMap 中的 Key 采用的是强引用方式。null
后,这时候 Entry
中的 ThreadLocal 理应被回收了,但是如果 Entry
的 key
被设置成强引用则该 ThreadLocal 就不能被回收,这就是将其设置成弱引用的目的。Entry#key
再次去key.get()
时,拿到的是 null
。Entry
是过期的,哪些 Entry
是非过期的。
set()
方法向下寻找可用 solt 桶位的过程中,如果碰到key == null
的情况,说明当前Entry
是过期数据,这个时候可以强行占用该桶位,通过replaceStaleEntry
方法执行替换过期数据的逻辑。cleanSomeSlots(int i, int n)
方法通过遍历桶位,也会将 key == null
过期数据清理掉。参考回答:
set()
或 get()
方法时,它会检测当前线程是否已经绑定了 ThreadLocalMap,如果已经绑定,则继续执行 set()
或 get()
方法的逻辑。面试官:那么线程的 ThreadLocalMap 会被多次创建吗?
参考回答:
index = threadLocalHashCode & (table.length - 1)
。这个算法实际就是取模运算:hash % tab.length
,而计算机中直接求余运算效率不如位移运算。hash & (tab.length- 1)
来寻找桶位。而实际上 hash % length
等于 hash & ( length - 1)
的前提是 length 必须为 2 的 n 次幂。例如,数组长度 tab.length = 8
的时候,3 & (8 - 1) = 3,2 & (8 - 1) = 2
,桶的位置是(数组索引) 3 和 2,不同位置上,不发生 hash 碰撞。
参考回答:
2/3
,当数组中,存储 Entry
节点的个数大于等于 2/3
时,会它并不会直接开始扩容。rehash()
方法,在该方法中,全面扫描整个数组,并将数组中过期的数据(key == null
)给清理掉,重新整理数组。Entry
节点的个数是否达到扩容阈值的3/4
,如果达到再调用真正扩容的方法resize();
面试官:那么你对
resize()
方法内部的扩容算法了解吗?
resize()
方法在真正执行扩容时,内部逻辑是先创建一个新的数组,新数组长度是原来数组长度的 2 倍。threshold
。threadLocals
字段引用,使其指向新数组。get()
方法中会先获取当前线程对象 t : Thread t = Thread.currentThread();
t
获取其独有的 ThreadLocalMap 数组:ThreadLocalMap map = getMap(t);
map
为空,则调用setInitialValue()
方法,该方法内部调用 initialValue();
方法获取 value
,并根据 当前线程t
和 value
调用 createMap(t, value);
方法创建 ThradLocalMap。map
不为空,则直接调用 ThreadLocalMap.Entry e = map.getEntry(this);
方法通过 this
(当前ThreadLocal对象)从 ThreadLocalMap 中获取对应封装数据的 Entry
节点。T result = (T)e.value;
得到要获取的线程变量副本的值。注意:
第 ④ 步中,通过当前 ThreadLocal 对象从 ThreadLocalMap 中获取对应封装数据的 Entry
节点时,内部逻辑是需要涉及到桶位寻址 index = threadLocalHashCode & (table.length - 1)
,如果获取的 inde
桶位中没有目标数据,这时候会执行``nextIndex(int i, int len)方法,**线性的向前或者向后去寻找目标数据所在的桶位,直到遍历整个数组仍未找到,则返回
null`**。
此外,在线性的向前、向后遍历数组寻找目标元素所在的桶位时,如果发现数据过期了(key == null
),则需要调用expungeStaleEntry(i);
方法进行一次探测式过期数据回收。
参考回答:
set()
方法向 ThreadLocalMap 中添加数据时,也是需要根据 Key (ThreadLocal对象) 的去寻址找到要插入的桶位下标 i = key.threadLocalHashCode & (len-1);
Entry e = tab[i];
,如果获取的 e
为 null
,则说明是空桶,直接讲 Key 和 Value 包装成 Entry
放入桶中即可:tab[i] = new Entry(key, value);
e
不为 null
,说明不是空桶,则需要从以下三种情况考虑:
Entry
的 Key 不是当前 ThreadLocal 对象,且不为 null
,则调用nextIndex(int i, int len)
方法线性查找下一个空桶位,并将新数据放入。Entry
的 Key 是当前 ThreadLocal 对象,则通过更新操作,将就 Entry
的 Value 值覆盖。Entry
的 Key 是null
,则说明当前 Entry
已经过期,需要执行 替换过期数据的逻辑: replaceStaleEntry(key, value, i);
。总结的面试题也挺费时间的,文章会不定时更新,有时候一天多更新几篇,如果帮助您复习巩固了知识点,还请三连支持一下,后续会亿点点的更新!
为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm
,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,有兴趣的小伙伴可以了解一下,当然,不管怎样博主的文章一直都是免费的~
作者:天神下凡
链接:http://www.phpheidong.com/blog/article/86721/179c43965c102947e293/
来源:php黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 php黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-4
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!