赞
踩
ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内,
需要重点强调的的是,不要拿ThreadLocal和synchronized做类比,sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问,用于数据共享。而ThreadLocal从本质上讲,无非是提供了一个“线程级”的变量作用域,它是一种线程封闭(每个线程独享变量)技术,用于数据隔离。
示例代码如下:
public class ThreadLocalTest { static ThreadLocal<String> localVar = new ThreadLocal<>(); static void print(String str) { //打印当前线程中本地内存中本地变量的值 System.out.println(str + " :" + localVar.get()); //清除本地内存中的本地变量 localVar.remove(); } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 localVar.set("localVar1"); //调用打印方法 print("thread1"); //打印本地变量 System.out.println("after remove : " + localVar.get()); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 localVar.set("localVar2"); //调用打印方法 print("thread2"); //打印本地变量 System.out.println("after remove : " + localVar.get()); } }); t1.start(); t2.start(); } } 输出如下: thread1 :localVar1 thread2 :localVar2 after remove : null after remove : null
下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量。
ThreadLocalMap实际上类似于一个HashMap,在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们。
除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量,即在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。。
set方法源码:
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
get方法源码: public T get() { //(1)获取当前线程 Thread t = Thread.currentThread(); //(2)获取当前线程的threadLocals变量 ThreadLocalMap map = getMap(t); //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量 return setInitialValue(); } private T setInitialValue() { //protected T initialValue() {return null;} T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //以当前线程作为key值,去查找对应的线程变量,找到对应的map ThreadLocalMap map = getMap(t); //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值 if (map != null) map.set(this, value); //如果map为null,说明首次添加,需要首先创建出对应的map else createMap(t, value); return value; }
ThreadLocalMap的数据结构,其实是个Entry类型的数组,每个Entry节点都保存一个键值对(key为ThreadLocal 实例的弱引用)。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。
类的构造函数有两个,一个为public方法,一个为private方法:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始长度为16的Entry数组
table = new Entry[INITIAL_CAPACITY];
// 用每个key的threadLocalHashCode和(1111)按位做与操作得到Entry应该放的下标
// 这样做的好处就是不管你threadLocalHashCode再大,计算结果也不会超过15
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 初始化Entry,并放入数组对应位置
table[i] = new Entry(firstKey, firstValue);
// Entry数组大小更改为1
size = 1;
// 根据容量重设扩容阈值
setThreshold(INITIAL_CAPACITY);
}
这个方法就是传入第一个ThreadLocal对象作为key,value作为值,构建一个ThreadLocalMap。
需要注意的是,ThreadLocalMap是懒创建的,也就是说直到有Entry需要加入才会调用此方法。
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } 这个构造方法由createInheritedMap方法调用,传入的参数是父线程的ThreadLocalMap。 将会创建一个ThreadLocalMap包括所有父map内的ThreadLocal
// 设置扩容阈值为容量的三分之二 private void setThreshold(int len) { threshold = len * 2 / 3; } // 下标i+1的方式从小往大下标方向递推 private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } // 下标i-1的方式从大往小下标方向递推 private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } // 将Entry数组扩容为以前大小的两倍,顺便清理发生hash碰撞时key已为null的entry。 // 具体做法是 // 1.创建新的Entry[] // 2.然后循环遍历老的Entry[] // 3.如果Entry元素存在就去获取弱引用的ThreadLocal实例, // 4.1此时如果ThreadLocal实例已经为空,代表已经被GC回收,那么直接把当前Entry置为null; // 4.2.1否则通过threadLocalHashCode和(新的数组长度-1)按位与的方式得到新的数组下标 // 4.2.2接着判断该下标位置是否已存在元素,若存在就反复调用nextIndex方法求新的下标 // 4.2.3将元素entry放入新的数组中的下标位置,并将临时元素计数器加一 // 5.更新扩容阈值,并将临时变量赋值给对应的实例变量。扩容完成。 private void resize() { // 1.创建新的Entry[],长度为旧数组两倍 Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; // 2.然后循环遍历老的Entry[] for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { // 3.如果Entry元素存在就去获取弱引用的ThreadLocal实例, ThreadLocal<?> k = e.get(); if (k == null) { // 4.1此时如果ThreadLocal实例已经为空,代表已经被GC回收,那么直接把当前Entry置为null; e.value = null; // Help the GC } else { // 4.2.1否则通过threadLocalHashCode和(新的数组长度-1)按位与的方式得到新的数组下标 int h = k.threadLocalHashCode & (newLen - 1); // 4.2.2接着判断该下标位置是否已存在元素,若存在就反复调用nextIndex方法求新的下标 while (newTab[h] != null) h = nextIndex(h, newLen); // 4.2.3将元素entry放入新的数组中的下标位置,并将临时元素计数器加一 newTab[h] = e; count++; } } } // 5.更新扩容阈值,并将临时变量赋值给对应的实例变量。扩容完成。 setThreshold(newLen); size = count; table = newTab; }
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 寻找新entry放入的适合位置。 // hash冲突时再hash方式为nextIndex从下标小的方式往大的方向递推 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // 如果计算出的下标位置存在entry // 且该ThreadLocal实例key和参数ThreadLocal相同,那就更新value。set结束 if (k == key) { e.value = value; return; } // 如果计算出的下标位置存在entry且该ThreadLocal实例key为null // 此时说明该entry的弱引用已经失效,就用生成新的entry替换。set结束 if (k == null) { replaceStaleEntry(key, value, i); return; } // 如果计算出的下标位置存在entry且该ThreadLocal实例key和参数ThreadLocal不同,就进入下一次循环 // 否则说明该位置e = null ,跳出循环 } // 此时e = null,也就是说该数组位置不存在entry // 用参数生成一个新的entry,放入此位置 tab[i] = new Entry(key, value); // 大小加一 int sz = ++size; // 如果 且 当前数组元素个数达到扩容阈值,就需要rehash if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
// 获取ThreadLocal实例对应的Entry // 如果没能匹配到就调用getEntryAfterMiss 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); } // 当没有找到ThreadLocal实例对应的Entry时调用该方法 // 这个方法也会在查找过程中顺便清理无效的弱引用Entry // i为数组下标,e为当前下标对应的Entry 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; }
// 替换失效的数组位置上的Entry,在此过程中将遇到的失效弱引用Entry移除 // key和value为新的值,staleSlot为匹配到的第一个失效Entry下标 private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // 倒推以检查当前运行中的已失效Entry。 // 我们一次清理整个运行,以避免由于垃圾收集器释放串联的refs(即,每当收集器运行时)不断的增量重复。 int slotToExpunge = staleSlot; // 这里得到的slotToExpunge是从后往前推的最后一个e!=null但是e.get()==null的下标 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(); // 如果匹配到键,那么我们需要将它与失效Entry交换以维护哈希表顺序。 // 然后可以将新陈旧的插槽或其上方遇到的任何其他陈旧插槽发送到expungeStaleEntry以删除或重新运行运行中的所有其他条目。 if (k == key) { // 如果存在该ThreadLocal实例的Entry,就覆盖该value e.value = value; // 这里staleSlot为匹配到的第一个失效Entry下标 // 赋值后tab[i]!=null但是tab[i].get()==null tab[i] = tab[staleSlot]; // 原staleSlot Entry替换为value更新后的e tab[staleSlot] = e; // slotToExpunge == staleSlot的情况是在前面倒推运算中没有找到失效的Entry if (slotToExpunge == staleSlot) // 注意这里i下标对应的元素是tab[staleSlot] slotToExpunge = i; // 移除slotToExpunge位置的Entry顺便移除一些失效Entry cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // 该Entry弱引用失效,且在前面倒推查找中没有匹配到失效的Entry if (k == null && slotToExpunge == staleSlot) // 那就把当前这个对应失效弱引用的i给slotToExpunge,然后继续循环 slotToExpunge = i; } // 走到这里说明没有匹配到ThreadLocal key // 把staleSlot处的value设为空(help gc) tab[staleSlot].value = null; // 该位置设为由新的ThreadLocal实例为key,新value的Entry tab[staleSlot] = new Entry(key, value); // 不相等说明找到了另外的失效的Entry位置,干掉他们并顺便清理出一点空间 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
// 清理一些Entry // 清理次数由当前数组大小是2的多少倍决定 private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; // 找到下一个下标位置,如果失效entry就干掉 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; } // 先清理所有失效的Entry // 如果 此时size还是大于之前的四分之三,就扩容 private void rehash() { expungeStaleEntries(); if (size >= threshold - threshold / 4) resize(); } // 遍历数组,移除失去弱引用的旧Entry private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; // 找到失去弱引用的Entry,将该Entry所在下标传入expungeStaleEntry将其干掉 if (e != null && e.get() == null) expungeStaleEntry(j); } } // 此方法是真正移除失去弱引用的Entry的方法,顺便移除遇到的碰撞位置的失效Entry // 参数staleSlot 是该Entry所在下标 // 返回下标i,此时tab[i]为null private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; // 数组原始长度 int len = tab.length; // 移除Entry的value和本身的引用 tab[staleSlot].value = null; tab[staleSlot] = null; // 数组大小减一 size--; Entry e; int i; // 循环的方式去掉弱引用失效的entry // 循环开始条件是让该下标对原始数组长度求模 // 循环结束条件是Entry为null // 每次循环完又再次nextIndex计算新下标 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { // 找到弱引用失效的entry,干掉 e.value = null; tab[i] = null; size--; } else { // 此时e的弱引用存在 // 我们已经熟悉了这种获取下标方式 int h = k.threadLocalHashCode & (len - 1); // 不相等表示存在hash冲突,放入的位置是用N次nextIndex计算后的新位置 // 这样做的目的我猜是因为这个位置已经发生冲突,所以认为此位置是易冲突位置 // 所以干脆把该位置的entry放到新的null位置 // 这样可以使下次放入元素时发生冲突的几率降低 if (h != i) { // 将e的来自于数组的强引用去掉 tab[i] = null; // 找到一个新的无entry的数组下标 while (tab[h] != null) h = nextIndex(h, len); // 将e移动到新的数组下标位置 tab[h] = e; } } } // 此时tab[i]为空 return i; }
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量)。但是InheritableThreadLocal类则可以使子类访问父类的本地变量,下面是该类的源码:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。
在前面的介绍中,可以知道ThreadLocal只是一个工具类,它为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,首先了解下几个概念:
ThreadLocalMap的数据结构,其实是个Entry类型的数组,它是继承自WeakReference的一个类,每个Entry节点都保存一个键值对,该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值就是通过ThreadLocal的set方法传递过来的值)
考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏。所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。实际上也是多线程的一种方式,是用空间换取时间,适合多线程但是线程变量不共享,每个线程的值各自独立的情况。ThreadLocal通常用private static修饰,可以将状态与该线程建立一对一的关系。
比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。
参考文章如下:
https://www.cnblogs.com/hup666/p/13053226.html
https://www.cnblogs.com/fsmly/p/11020641.html
https://www.cnblogs.com/BigJunOba/p/8980007.html
https://blog.csdn.net/qq_39188150/article/details/111416107
https://blog.csdn.net/u011507568/article/details/96425898
https://blog.csdn.net/tianjindong0804/article/details/85597215
https://blog.csdn.net/qq_38254635/article/details/120288780
http://www.what21.com/u/10004/5674370938898358715.htm
http://www.threadlocal.cn/
https://blog.csdn.net/weixin_41685207/article/details/112121372
https://blog.csdn.net/u010445301/article/details/111322569
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。