- private void replaceStaleEntry(ThreadLocal < ?>key, Object value, int staleSlot)
第一个参数就是我们的 key,估摸着还是用来计算 hash 值找位置的;第二个就是要放的 value 了;第三个 staleSlot 呢?我们上面好像是找到了一个 key 为 null 的 entry 节点吧?没错,这个 staleSlot 就是这个节点在 tab 中的位置了。然后从这个 staleSlot 节点开始往前找,如果发现某个 entry 不为空,但 key 等于 null,用 slotToExpunge 记录下它的位置,直到往前找到一个 entry 为 null 的节点停止。这个 slotToExpunge 是用来干嘛的呢?后面会提到。
我们接着看。往前找完之后,我们又从 staleSlot 的下一个节点开始往后找,如果发现了某个节点的键值等于我们的 key。我们是不是应该用我们的 value 替换掉这个位置原先的值呢?好像是应该替换。但是别忘记了前面还有个 key 为 null 的 entry 节点呢!由于之前 key 为 null 的节点和当前节点计算出来 hash 值其实是一样的。这里我们将 e 节点的值更新为最新的 value 后,互换 tab[i] 和 entry 的位置。这一步的目的是什么呢?我猜大概是这样的,因为 ThreadLocalMap 是根据线性探测法来解决冲突的,因此可能会出现 key 的哈希值相同但散落位置不连续的情况。为了在一定程度上提高查找哈希值相同 entry 节点的效率,交换一下位置会是更好的选择。同时接下来会执行 cleanSomeSlots() 方法。我们上面的 for 循环会一直往后找,直到发现一个 null 节点为止。如果找到了 null 节点,那就说明按照线性探测法找不到这个节点了啊!那咋办呢?staleSlot 节点不是空着呢么。直接塞进去不就完事了。。
最后一句又调用了 cleanSomeSlots() 方法。下面就轮到它了。。
- private int expungeStaleEntry(int staleSlot) {
- Entry[] tab = table;
- int len = tab.length;
- // expunge entry at staleSlot
- tab[staleSlot].value = null;
- tab[staleSlot] = null;
- size--;
- // Rehash until we encounter null
- Entry e;
- int i;
- for (i = nextIndex(staleSlot, len);
- (e = tab[i]) != null;
- i = nextIndex(i, len)) {
- ThreadLocal<?> k = e.get();
- if (k == null) {
- e.value = null;
- tab[i] = null;
- size--;
- } else {
- int h = k.threadLocalHashCode & (len - 1);
- if (h != i) {
- tab[i] = null;
- // Unlike Knuth 6.4 Algorithm R, we must scan until
- // null because multiple entries could have been stale.
- while (tab[h] != null)
- h = nextIndex(h, len);
- tab[h] = e;
- }
- }
- }
- return i;
- }
在看 cleanSomeSlots() 方法前,还得看 expungeStaleEntry()。有时候不得不说,好的 java 命名规范,真的是很重要啊!看到这个方法的名字就知道它大概是用来清理过期节点的。回想一下,有什么节点是需要我们的清理的吗?好像有。。前面是不是有找到过 key 为 null 的 entry 节点啊?这个 key 为 null 的节点好像没啥用啊!
- cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
还记得这个 slotToExpunge 吗?不记得了的话往前翻一翻。这个 slotToExpunge 位置指向了一个 key 为 null 的 entry 节点。既然知道这个节点是没用的,那它就应该被回收。这里就很直接粗暴了,直接把它直接 null 以待后面垃圾回收器清理。清理完之后,又是一个 for 循环。如果 key 为 null,将该 entry 置为 null。如果不为 null,重新计算一下 hash 值,如果位置与当前位置不同,需要重新找一个位置放该节点。当然也是利用线性探测法了,找到连续位置后面第一个为 null 的节点放置。
最后返回的节点为从 slotToExpunge 往后的第一个值为 null 的 entry 节点。
再看看 cleanSomeSlots 方法主体。
- private boolean cleanSomeSlots(int i, int n) {
- boolean removed = false;
- Entry[] tab = table;
- int len = tab.length;
- do {
- 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);
- return removed;
- }
这个方法顾名思义是用来清理某些节点的。清理啥节点呢?还是那些不为 null 但是 key 为 null 的节点。参数 n 决定了 for 循环要执行的次数。>>> 在 java 中是无符号位移的意思,也就是说如果每次循环 tab[i] 均不需要清理,最多会执行 logn 次。如果有需要清理的节点,就会调用 expungeStaleEntry() 方法去回收这个节点。
上面说了这么一大堆,终于把 ThreadLocalMap 的 set() 方法说完了。下面接着来看 getEntry() 方法。
- private Entry getEntry(ThreadLocal<?> key) {
- int i = key.threadLocalHashCode & (table.length - 1);
- Entry e = table[i];
- if (e != null && e.get() == key)
- return e;
- else
- return getEntryAfterMiss(key, i, e);
- }
getEntry() 方法比较简单。先根据 key 值计算出对应在 table 中的位置,如果 table[i] 的 key 值和我们的参数 key 相同,直接返回 table[i]; 反之,调用 getEntryAfterMiss() 方法。
- private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
- Entry[] tab = table;
- int len = tab.length;
- while (e != null) {
- ThreadLocal<?> k = e.get();
- if (k == key)
- return e;
- if (k == null)
- expungeStaleEntry(i);
- else
- i = nextIndex(i, len);
- e = tab[i];
- }
- return null;
- }
进入到这个方法的情况可能如下三种:
getEntry() 方法会被 ThreadLocal 的 get() 方法调用,我们会在稍后的 ThreadLocal 源码的讲解中再谈。
介绍 ThreadLocalMap 用了不少的篇幅啊!下面就来看看我们 ThreadLocal 啦!关于 ThreadLocal 的方法网上已经有太多太多的文章介绍了。不过这里我们还是简单的结合我们上面所说的 ThreadLocalMap 来总结一下!
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
当我们调用 ThreadLocal 对象的 set 方法时,程序会获取当前线程,并将其作为作为参数传递给 getMap() 方法。
- Thread.java
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ThreadLocal.java
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
查看 Thread 类源码可以发现,Thread 类中包含了一个 ThreadLocalMap 对象,这个家伙我们上面已经花了很大的篇幅来说了,简单的说它的 key 为 ThreadLocal 的弱引用,而 value 为待保存的对象。至于为什么是弱引用,大家自己去 google 下。
继续说 getMap() 方法。getMap() 方法很简单:返回当前线程中的 ThreadLocalMap 对象。
createMap() 方法在这里不细说。它会初始化当前线程的 ThreadLocalMap 对象,并将当前需要保存的对象放入 ThreadLocalMap 中,key 值为当前线程的弱引用对象。 小结一下:ThreadLocal 的 set 方法会获取当前线程的 ThreadLocalMap 对象,如果 TreadLocalMap 对象不为空,则将当前线程的弱引用作用 key,待保存对象作为 value 保存起来;若 ThreadLocalMap 对象为 null,则会先初始化,再放入键值对。
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();
- }
知道了怎么放,接下来聊一聊怎么取。眼尖的朋友们肯定已经发现了,这里又出现了 ThreadLocalMap 对象。那是自然,因为我们上面不就是往 ThreadLocalMap 里面放的吗!还记得 ThreadLocalMap 里面存了啥不?不记得的往上翻一翻。
如果 ThreadLocalMap 对象不为空,当前线程作为 key 值,从 ThreadLocalMap 中取出来了一个 ThreadLocalMap.Entry 对象。这个 getEntry() 方法我们在上面已经已经介绍过了,可能再返回去看看。当然了,肯定有人要问!我们刚才放的时候放的明明不是 ThreadLocalMap.Entry 对象!这咋回事呢?
- ThreadLocalMap.java
- static class Entry extends WeakReference<ThreadLocal> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal k, Object v) {
- super(k);
- value = v;
- }
- }
实际上,在 ThreadLocalMap 中有一个静态类,它名叫 Entry,继承了 WeakReference 类。再看看 Entry 的构造方法。如果调用 Entry 的 get 方法,实际上拿到的是 ThreadLocal 对象的弱引用对象。是不是很熟悉?上面的 set 方法有聊到过。
继续说上面的 get() 方法。当我们拿到了 Entry 对象后,如果 Entry 对象不为空,直接返回 Entry 对象的 value 值,即我们想要的值。
那么如果 ThreadLocalMap 为空呢?则会执行 setInitialValue() 方法。光看名字,你肯定觉得它无非执行了两步操作:1. 初始化对象;2. 将初始化后的对象塞入 ThreadLocalMap 对象中;3. 返回初始化后的对象。那我们来看看我们的猜想对不对呢?
- 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);
- return value;
- }
除了第一句代码,后面的是不是好像都在哪里见过啊?可不是!不就是上面的 set() 方法吗?这个方法我们只需要关注 initialValue() 方法... 而这个 initialValue() 方法是需要我们自己重写的。
来源: https://www.cnblogs.com/cfyrwang/p/8166369.html