什么是ThreadLocal?
ThreadLocal是线程变量,每个线程可以在一个ThreadLocal里面存放一个变量,这个变量是线程安全的,除了ThreadLocal还可以用栈的本地变量或者锁来保证线程安全,并且可以用于方法间的数据传递。
ThreadLocalMap是ThreadLocal的一个内部类,用于保存数据,key是ThreadLocal的一个弱引用(可以有很多个ThreadLocal),那如果是强引用就不会被回收了,value是存放的变量值
// 强引用ThreadLocal<Object> threadLocal = new ThreadLocal<>();threadLocal.get();// 弱引用new ThreadLocal<>().get();
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有自己的ThreadLocalMap
简单来说就是每个线程一个ThreadLocalMap,存放很多个 <不同的ThreadLocal,不同的变量值>
应用场景
保存某个信息在线程的任意时刻可以使用,如保存用户user信息,可以获取userId或者其他什么信息
源码解析
hash冲突
和HashMap的链地址法不同,ThreadLocal采用nextIndex()和prevIndex()往前后找位置
nextIndex()和prevIndex()
向后或者向前遍历
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}
set()
-
计算下标位置
-
下标位置就是空白的,则直接加入
-
下标位置有节点,则往后遍历,如果发现key相同的节点,进行值替换
-
如果没有过期节点,一直找到空白位置
-
如果有过期节点,进行替换,并且进行清理
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;// 对于同一个线程的下标位置都会相同 int i = key.threadLocalHashCode & (len-1);// 找到合适的位置存放新节点for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();// key相同替换if (k == key) {e.value = value;return;}// 有节点,但是节点的key为null,说明节点被回收了,替换之if (k == null) {replaceStaleEntry(key, value, i);return;}}// 找到了空白的位置,准备加入新节点tab[i] = new Entry(key, value);int sz = ++size;// 超过阈值进行扩容if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}
replaceStaleEntry()
过期节点替换和垃圾清理(往前找到空白前的第一个过期节点作为开始清理点)
slotToExpunge:从这个位置开始垃圾清理
staleSlot:需要替换的过期节点位置
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;// 遍历检测还有没有其它过期的元素,直到null,用slotToExpunge记录开始点,之后用于清理数据int slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)// 当前节点过期了,更新slotToExpungeslotToExpunge = i;// 从staleSlot往后遍历for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// 这里主要是为了配合调用者方法,也有一个key相同的处理方式if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;// 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理if (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}// 更新slotToExpungeif (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}// 替换staleSlot位置为新节点,也是这个方法的主要目的tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理if (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}
垃圾清理
expungeStaleEntry()
线性清理,遇到空白则停止清理,这里的staleSlot实际上是传进来的slotToExpunge
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry 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 {// 进行rehash重新存放位置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()
每次循环n/=2,n是table[]数组的长度,直到n==0停止清理,也是线性清理
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;}
扩容
// threshold 默认 2/3 * lenif (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();private void rehash() {// 从开头开始清理所有过期节点,然后移动零散节点到一起expungeStaleEntries();// 因为有些过期节点被清理,减少扩容的阈值判断if (size >= threshold - threshold / 4)// 2倍扩容,重新rehashresize();}