背景
-
探究ThreadLocal源码中核心方法set的工作过程及其实现细节。
过程
- set的入口函数
注释含义:把当前线程中的本地变量值,设置到具体的容器entry[]中。大多数ThreadLocal的子类不要重写set方法去设置初始值。因为initialValue方法给线程本地变量设置值。
细节:
- 获取当前线程
- 获取当前线程属性ThreadLocalMap
- 判断当前线程属性ThreadLocalMap是否为null
- 如果不为空,则直接设置值
- 如果为空,则创建ThreadLocalMap
- 创建ThreadLocalMap
注释含义:创建一个与ThreadLocal关联的map容器。
细节:
-
调用ThreadLocalMap的有参构建器
注释含义:构造一个新的map.初始值是(firstKey, firstValue)。ThreadLocalMap的构建是懒加载的。当至少有一个entry实例需要放入到map中的时候,我们才创建一个map容器。
细节:
- 创建table,也就是给Entry[]赋值。其中,INITIAL_CAPACITY是16
- 通过魔数0x61c88647及其长度算得tab中的下脚标值
- 给这个table[i]赋值。
- 指定大小为1,但是长度是16
- 设置扩容阈值(为容量的三分之二大小)
注释含义:ThreadLocalMap持有Entry[],而这些Entry又继承了WeakReference。使用引入属性作为key,而这个key总是ThreadLocal对象。当key为null的时候(比如执行entry.get == null)意味着key将永远不会被引用到了,因此entry实例需要从这个table中移除。在后面的代码中,把这样的entry叫做 stale entry。
细节:
- 代码执行红框中的构建函数
- 先调用父类的构建器
- 然后给Object value赋值即可
因此,我们说entry中的key,ThreadLocal是弱引用。
-
不创建ThreadLocalMap
细节:
1. 先忽略for循环中执行的逻辑,new Entry(key, value)上面逻辑已说明。 2. 执行cleanSomeSlots,表示把entry不为null,而key为null的移除掉。 3. 判断,如果没有移除且sz大于10(16 * 2 / 3), 则执行rehash()函数。 复制代码
-
cleanSomeSlots(i, sz)
- 此方法的注释
注释含义:
试探性地扫描数组中的元素,以便找到无效entry实例。当我们创建一个新的entry实例的时候,这个方法就会被调用到。扫描的次数是以2为底数,长度为指数的log函数。如果我们要扫描所有数组的内容的话,我们就需要变量整个数组,这会花费O(N)时间,而我们垃圾收集器是(fast but retains garbage),为了在两者种取到平衡。选择log函数次数扫描。
从名字就可以看出,some,而不是all。
-
此方法的源码
细节:
先不看if中代码段,重新给n赋值为长度的值。
看循环是如何判断的?
这个时候,假如i是14,通过 i = nextIndex(i, len),i = 15,tab[15]是null。
进行while循环判断(n >>>= 1),这个时候n(16)除以2就是8,8 != 0。 继续执行do,这个时候通过 i = nextIndex(i, len)后,i = 0,tab[0]是null。
进行while循环判断(n >>>= 1),这个时候n除以2就是4,4 != 0 。 继续执行do,这个时候通过 i = nextIndex(i, len),i = 1, tab[1]是null。
进行while循环判断(n >>>= 1),这个时候n除以2就是2,2 != 0 。 继续执行do,这个时候通过 i = nextIndex(i, len),i = 2, tab[2]是null。
进行while循环判断(n >>>= 1),这个时候n除以2就是1,1 != 0 。 继续执行do,这个时候通过 i = nextIndex(i, len),i = 3, tab[3]是null。
进行while循环判断(n >>>= 1),这个时候n除以2就是0,0 != 0 。退出循环。
一旦进入if判断逻辑中。那么n的值又被设置成长度值16了。然后又循环上面的整个逻辑判断过程。
expungeStaleEntry(i)
/** * Expunge a stale entry by rehashing any possibly colliding entries * lying between staleSlot and the next null slot. This also expunges * any other stale entries encountered before the trailing null. See * Knuth, Section 6.4 * * @param staleSlot index of slot known to have null key * @return the index of the next null slot after staleSlot * (all between staleSlot and this slot will have been checked * for expunging). */ 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; } 复制代码
-
rehash()
/** * Re-pack and/or re-size the table. First scan the entire * table removing stale entries. If this doesn't sufficiently * shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } 复制代码
-
expungeStaleEntries()
/** * Expunge all stale entries in the table. */ 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); } } 复制代码
-
resize()
/** * Double the capacity of the table. */ private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; 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; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } 复制代码
-
回到for循环体中的内容
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } 复制代码
小结
-
阅读源码需要读注释。注释甚至比源码实现细节更加重要,因为源码注释讲述的是为什么有这个方法以及一些注意事项。不然,只是知其然,而不知其所以然。不仅获取不到好处,反而受到它的牵累。
-
当有足够的基础知识了,比如理解ThreadLocal的工作过程,理解魔数相关知识,理解环形数组结构,理解解决hash碰撞原理,那么读源码实现细节,就是一件自然而然的事情。