ThreadLocal源码学习笔记
1.ThreadLocal的介绍
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
ThreadLocal的使用
// An highlighted block
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>();
sThreadLocal.set();
sThreadLocal.get();
2.threadLocal原理
作为一个存储数据的类,关键点就在get和set方法。
ThreadLocal中的get和 set方法的源码
//set 方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//实际存储的数据结构类型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中维护了一个ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Thread源码,thread中持有一个ThreadLocalMap 对象
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
从上面代码可以看出每个线程持有一个ThreadLocalMap对象,ThreadLocalMap是ThreadLocal 中的一个内部类。每一个新的线程Thread存值时都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
ThreadLocal中内部类ThreadLocalMap的源码
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
//同时让ThreadLocal和储值形成key-value的关系
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//内部成员数组,INITIAL_CAPACITY值为16的常量
table = new Entry[INITIAL_CAPACITY];
//位运算,结果与取模相同,计算出需要存放的位置
//threadLocalHashCode比较有趣
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
通过上面的代码不难看出在实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。
前面讲过每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法可以理解成每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
例如使用时
//在某一线程声明了三种类型的ThreadLocal
ThreadLocal<Student> threadLocalA = new ThreadLocal<Student>();
threadLocalA.set(new Student("java"))
ThreadLocal<Student> threadLocalB = new ThreadLocal<Student>();
threadLocalB.set(new Student("c"))
ThreadLocal<Student> threadLocalC = new ThreadLocal<Student>();
threadLocalC.set(new Student("php"))
结合上面threadLocal的set方法的源码,调用threadLocal的set方法存值时,执行的是map.set(this, value),所以是将值存进ThreadLocalMap中的数组中的。这里三个ThreadLocal对象存储值时,都在一个线程,所以存储的值是在同一个ThreadLocalMap中的同一个Entry数组中,只是存在不同位置。
关于存储在数组哪个位置,看ThreadLocalMap中的set方法源码
//ThreadLocalMap中set方法。
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);
//遍历tab如果已经存在则更新值
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;
}
}
//如果上面没有遍历成功则创建新值
tab[i] = new Entry(key, value);
int sz = ++size;
//满足条件数组扩容x2
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
可以看到在ThreadLocalMap中的set方法与构造方法能看到以下代码片段。
int i = key.threadLocalHashCode & (len-1)
简而言之就是将threadLocalHashCode进行一个位运算(取模)得到索引i。
threadLocalHashCode是threadLocal中的成员变量
//ThreadLocal中threadLocalHashCode相关代码.
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);
}
也就是说,每次new ThreadLocal对象时都会初始化一个threadLocalHashCode值,而这个东西是每次创建都会自增一个值增量为0x61c88647,所以不同ThreadLocal对象threadLocalHashCode值不同。
总结:
1.每个线程thread中都持有一个ThreadLocalMap对象;ThreadLocalMap中有一个Entry[]数组;数组的i位置存的tab[i] = new Entry(key, value),其中key是当前ThreadLocal实例对象,value就是存的值,i是ThreadLocal实例化时自增的threadLocalHashCode和数组长度计算得到的角标;
2.每个线程共用同一个ThreadLocalMap对象,即共用一个Entry[]数组,不同的ThreadLocal(例如上面的threadLocalA ,threadLocalB,threadLocalC )存值时存在同一数据不同位置
3.拓展:
3.1.关于索引 int i = key.threadLocalHashCode & (len-1);
这其实是一个取模计算,计算当前ThreadLocal实例在entry数组table中的下标,为了使数组中的元素尽量散开
hashCode = hashCode +HASH_INCREMENT每次new一个元素(threadLocal),hashCode 自增0x61c88647
元素散列位置(数组下标)i = hashCode & (length-1)
使用这个方法来检测该算法的优略
private static void hashCode(Integer length){
int hashCode = 0;
for(int i=0;i<length;i++){
hashCode = i*HASH_INCREMENT+HASH_INCREMENT;//每次递增HASH_INCREMENT
System.out.print(hashCode & (length-1));//求散列下标,算法公式
System.out.print(" ");
}
System.out.println();
}
public static void main(String[] args) {
hashCode(16);//初始化16
hashCode(32);//后续2倍扩容
hashCode(64);
}
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 --》Entry[]初始化容量为16时,元素完美散列
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0–》Entry[]容量扩容2倍=32时,元素完美散列
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 --》Entry[]容量扩容2倍=64时,元素完美散列
3.2.为什么要使元素散列开,直接按照顺序 table[0],table[1] 存值不就行了?
为了提高效率,存值时如果table[i]位置被占了,要尽快的找到下一个能用的空位,例如上面的存储顺序table[7],table[14],如果table[7]位置被占了,就会去找table[8]位置,如果空缺,就使用table[8]。如果使用 table[0],table[1]这种方式,在数据多了之后就会导致大量遍历数组,效率低下。取值的时候也是一样,快速定位到 table[i]位置,没有就找隔壁位置。
3.3.table[i]位置被占怎么导致的,及快速找下一个空位
public static void main(String[] args) {
new Thread("新线程") {
public void run() {
Student student1 = new Student();
student1.setName("java");
ThreadLocal<Student> threadLocal1 = new ThreadLocal<>();
threadLocal1.set(student1);
ThreadLocal<Student> threadLocal2 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal3 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal4 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal5 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal6 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal7 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal8 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal9 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal10 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal11 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal12 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal13 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal14 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal15 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal16 = new ThreadLocal<>();
ThreadLocal<Student> threadLocal17 = new ThreadLocal<>();
threadLocal17.set(student1);
}
}.start();
}
ThreadLocalMap中的table数据初始化长度为16,第一次new ThreadLocal对象,并且set值,这时根据i = hashCode & (length-1)会生成索引i=10,然后每new一个ThreadLocal对象,hashCode 增加,table数组长度并没有扩容(用3/4时扩容),第17个ThreadLocal对象就会生成的索引和第一个样了。
如图断点调试
第一个threadLocal存值
创建ThreaLocalMap 对象
第一个存的位置i =10
第17个threadLocal塞值
已有ThreadLocalMap 直接调用set方法
发现索引 i =10,和threandLocal1 的索引一样,这时就会往后遍历数组,索引 i+1,如果该位置有值但是key和本次的threandLocal17一样,就替换原值,如果该位置没值,就直接使用
发现 i 变成了11,快速的找到了下一个空位
参考
https://www.jianshu.com/p/3c5d7f09dfbd
https://blog.csdn.net/qq_41652863/article/details/96132710