目录java
ThreadLocal顾名思义理解为线程本地变量,这个变量只在这个线程内,对于其余的线程是隔离的,JDK中对ThreadLocal的介绍:算法
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its{@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).数组
大意是ThreadLocal提供了线程局部变量,只能经过ThreadLocal的set方法和get方法来存储和得到变量。数据结构
ThreadLocal类结构以下:多线程
能够看到ThreadLocal有内部类ThradLocalMap,ThreadLocal存储线程局部对象就是利用了ThreadLocalMap数据结构,在下面的源码分析也会先从这里开始。ide
ThreadLocalMap静态内部类Entry是存储键值对的基础,Entry类继承自WeakReference(为何用弱引用在后面解释),经过Entry的构造方法代表键值对的键只能是ThreadLocal对象,值是Object类型,也就是咱们存储的线程局部对象,经过super调用父类WeakReference构造函数将ThreadLocal<?>对象转换成弱引用对象
ThreadMap存储键值对的原理与HashMap是相似的,HashMap依靠的是数组+红黑树数据结构和哈希值映射,ThreadMap依靠Entry数组+散列映射,ThreadLocalMap使用了Entry数组来保存键值对,Entry数组的初始长度为16,键值对到Entry数组的映射依靠的是int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
,经过ThreadLocal对象的threadLocalHashCode与(INITIAL_CAPACITY - 1)按位相与将键值对均匀散列到Entry数组上。函数
static class ThreadLocalMap { // 键值对对象 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //初始Entry数组大小 private static final int INITIAL_CAPACITY = 16; //Entry数组 private Entry[] table; //ThreadLocalMap实际存储键值对的个数 private int size = 0; //数组扩容阈值 private int threshold; // Default to 0 //阈值为数组长度的2/3 private void setThreshold(int len) { threshold = len * 2 / 3; } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } //构造一个ThreadLocalMap对象,并把传入的第一个键值对存储 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } }
ThreadLocal做为作为键值对的键经过常量threadLocalHashCode
映射到Entry数组,threadLocalHashCode
初始化时会调用nextHashCode()
方法,就是在nextHashCode
的基础上加上0x61c88647
,实际上每一个ThreadLocal
对象的threadLocalHashCode
值相差0x61c88647
,这样生成出来的Hash值能够较为均匀的散列到2的幂次方长度的数组中,具体可见这篇文章为何使用0x61c88647
因为采用的是散列算法,就须要考虑Hash冲突的状况,HashMap解决Hash冲突的方法是链表+红黑树,ThreadLocalMap解决方法是linear-probe(线性探测),简单来讲若是散列对应的位置已经有键值对占据了,就把散列位置加/减一找到符合条件的位置放置键值对。源码分析
// final常量,一旦肯定再也不改变 private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } //构造方法 public ThreadLocal() { }
简要介绍完了内部类ThreadLocalMap后,set方法属于ThreadLocal,首先得到与线程Thread绑定的ThreadLocalMap对象,再将ThreadLocal和传入的value封装为Entry键值对存入ThreadLocalMap中。注意,ThreadLocalMap对象是在线程Thread中声明的:
ThreadLocal.ThreadLocalMap threadLocals = null;
post
public void set(T value) { //得到当前线程对象 Thread t = Thread.currentThread(); //得到线程对象的ThreadLocalMap ThreadLocalMap map = getMap(t); // 若是map存在,则将键值对存到map里面去 if (map != null) map.set(this, value); //若是不存在,调用ThreadLocalMap构造方法存储键值对 else createMap(t, value); } //返回线程t中声明的Thread ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //利用ThreadLocal的threadLocalHahsCode值散列 int i = key.threadLocalHashCode & (len-1); //若是散列的位置不为空,判断是不是哈希冲突 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //若是此位置的键值对的键与传入的键相同,覆盖键值对的值 if (k == key) { e.value = value; return; } //键值对的键为空,说明键ThreadLocal对象被回收,用新的键值对代替过期的键值对 if (k == null) { replaceStaleEntry(key, value, i); return; } } //散列位置为空,直接存储键值对 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
得到当前线程中保存的以ThreadLocal对象为键的键值对的值。首先获取当前线程关联的ThreadLocalMap,再得到以当前ThreadLocal对象为键的键值对,map为空的话返回初始值null,即线程局部变量为null,学习
public T get() { //获取与当前线程绑定的ThreadLocalMap Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //map不为空,获取键值对对象 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private Entry getEntry(ThreadLocal<?> key) { //散列 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; //判断散列位置的键值对是否符合条件:e.get()==key if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } //线性探测寻找key对应的键值对 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; }
从ThreadLocalMap中移除键值对,通常在get方法取出保存的线程局部变量后调用remove方法防止内存泄露。
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
键值对对象Enry的键是ThreadLocal对象,可是使用WeakReferrence虚引用包装了的,虚引用相对于咱们常用的String str = "abc"
这种强引用来讲对GC回收对象的影响较小,如下是虚引用的介绍:
WeakReference是Java语言规范中为了区别直接的对象引用(程序中经过构造函数声明出来的对象引用)而定义的另一种引用关系。WeakReference标志性的特色是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象以外全部的对象解除引用后,该对象即可以被GC回收),只不过在被对象回收以后,reference实例想得到被应用的对象时程序会返回null。
若是Entry的键使用强引用,那么咱们存入的键值对即便线程以后再也不使用也不会被回收,生命周期将变得和线程的生命周期同样。而使用了虚引用以后,做为键的虚引用并不影响ThreadLocal对象被GC回收,当ThreadLocal对象被回收后,键值对就会被标记为stale entry(过时的键值对),再下一次调用set/get/remove方法后会进行 ThreadLocalMap层面对过时键值对进行回收,防止发生内存泄漏。
注意:当咱们使用了set方法存入局部变量后,若是不进行get/remove,那么过时的键值对没法被回收,因此建议在get取出存储变量后手动remove,能够有效防止内存泄漏。
ThreadLocal实现了存储线程局部变量,ThreadLocal的实现并非HashMap<Thread,Object>以线程对象为键,而是在线程内部关联了一个ThreadLocalMap用于存储键值对,键值对的键是ThreadLocal对象,因此ThreadLocal对象自己是不存储内容的,而是做为键与存储内容构成键值对。