jdk1.7之前,由ThreadLocal维护一个hashmap,每一个键值对的key 为 当前线程,value 为 分配到的‘’资源’‘
jdk1.8之前,由各自线程维护一个map,每一个键值对的key 为 threadlocal ,value 为 分配到的资源
这样做的优势是: map的生命周期就和线程一样,同时生成同时销毁 ;
map里面的key 是 一个弱引用 ,有利于GC回收,并且站在map的角度的话,能够知道哪个threadlocal是被回收了的
//每创建一个ThreadLocal对象时, 会为每一个分配到资源的线程分配一个threadLocalHashCode值,
//每一个threadLocalHashCode值之间间隔一个黄金分割点。
private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode =new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {AndAdd(HASH_INCREMENT);
获取每一个线程与之关联的threadLocal对象分配到的资源(变量),这个变量只有当前线程能够访问到
public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap//然后通过this获取entryset,然后通过entryset获取到value(value是当前线程分配到的变量)ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = Entry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//如果当前线程的map为空,或者当前线程分配到的value为空就进行初始化分配return setInitialValue();
}
//如果当前线程的map不为空,就会在当前线程map对象的entry的value值里放置一个 刚刚threadlocal初始化的与当前线程相关联的变量
//如果当前线程的map为空,就创建一个map
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的threadLocalMap对象ThreadLocalMap map = getMap(t);//条件成立:说明当前线程的threadLocalMap已经初始化过了if (map != null)//调用threadLocalMap.set方法 进行重写 或者 添加。map.set(this, value);else//执行到这里,说明当前线程还未创建 threadLocalMap对象。//参数1:当前线程 参数2:线程与当前threadLocal相关的局部变量createMap(t, value);
}
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != ve(this);
}
ThreadLocal 的方法比较简单, 但是这些方法里面都离不开一个东西,就是ThreadLocalMap ,所有下面是ThreadLocalMap的探究
在ThreadLocal的get()方法中调用了这个方法,来通过threadlocal得到entry
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//e == null 或者 key不相同(发生了hash冲突)return getEntryAfterMiss(key, i, e);
}
从当前位置线性向后寻找key == this.key
→ 如果在这个过程找到就返回key
没有找到的话就继续向后寻找
并且在找的过程中如果遇到 key == null的情况,说明弱引用已经被回收,需要执行一次探测过期数据回收
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);elsei = nextIndex(i, len);e = tab[i];}return null;
}
探测过期数据回收,如果当前key == null,就帮助GC回收,然后从下一个位置开始遍历整个数组帮助回收所有key == null的值
如果当前key != null,就去优化当前数据的位置**(** 因为当前位置的entry可能是hash冲突后,偏移到这里的,如果前面哈希冲突的entry有很多并且都给回收了的话,那么就需要优化一下当前entry的位置了。重新计算一下hash值,然后重新从hash值定位到的 i 出发,找到个合适的位置放下当前entry ) ,这样子做的话会使得查询性能更好,能使得entry离正确位置更近
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlot//回收当前位置的entrytab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;//遍历entry数组for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();//回收数组中 key == null的值if (k == null) {e.value = null;tab[i] = null;size--;}//key != null, 优化一下当前entry的位置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;
}
ThreadLocal的set()方法调用了这个set()方法,如果是在for循环里面,执行的其实是替换逻辑,退出for循环,执行的就是set一个新值逻辑
set的过程中,如果 1. k == key,说明threadlocal已经存在,只需要将值替换就行了
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();//1.if (k == key) {e.value = value;return;}//2.if (k == null) {replaceStaleEntry(key, value, i);return;}}//3.tab[i] = new Entry(key, value);int sz = ++size;//if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}
从key == null的位置出发,向后寻找k == key的位置,找到就交换下位置,找不到就在当前位置新建一个entry
然后用一个变量slotToExpunge来标记null,用于探测式清理
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;//这个参数是用于后面探测式清理的时候用的int slotToExpunge = staleSlot;清除 //向前遍历,如果找到threadlocal == null,就将slotToExpunge标记到那个位置for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;//向后遍历,替换key值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;//交换一下位置,将被替换key那个位置,与起始查找位置换一下,我觉得有利于查询tab[i] = tab[staleSlot];tab[staleSlot] = e;清除 //与清除逻辑有关//如果在之前向前遍历的时候,slotToExpunge没有变化 --》 就将slotToExpunge设置为当前为null的位置i(因为交换过位 置了)if (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}清除 //找的时候又找到一个null,这个也是与清除逻辑有关//将slotToExpunge设置为当前为null的位置iif (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}//如果向后遍历也没有找到相同的key值,就在进入这个方法找到null的那个位置,创建一个新的entrytab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);清除 //这个也是清除逻辑if (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
启发–》其实就是帮助探测式清除找到下一段要清除的一段(因为探测式清除,到entry==null的时候就会停止清除了)
每启发一次探测式清除,就会将n重新设置为Entry[] tab 的长度。
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;
}
本文发布于:2024-02-01 23:46:42,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170680840639982.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |