ThreadLocal--重点笔记
一 什么是ThreadLocal
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例
通常来说都是private static类型的,用于关联线程和线程上下文。
特性
1,线程安全: 在多线程并发的场景下保证线程安全
2,传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
3,线程隔离: 每个线程的变量都是独立的,不会互相影响
二 ThreadLocal的内部结构
但是,JDK后面优化了设计方案,在JDK8中 ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。
具体的过程是这样的:
1,每个Thread线程内部都有一个Map (ThreadLocalMap),
2,Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
3,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。,
4,对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干
扰。

三 内存泄漏
待整理
四 hash冲突的解决
从ThreadLocal的set() 方法入手
public void set(T value) { 2 Thread t = Thread.currentThread(); 3 ThreadLocal.ThreadLocalMap map = getMap(t); 4 if (map != null) 5 //调用了ThreadLocalMap的set方法 6 map.set(this, value); 7 else 8 createMap(t, value); 9 } 10 11 ThreadLocal.ThreadLocalMap getMap(Thread t) { 12 return t.threadLocals; 13 } 14 15 void createMap(Thread t, T firstValue) { 16 //调用了ThreadLocalMap的构造方法 17 t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); 18 }
ThreadLocalMap中的set方法
private void set(ThreadLocal<?> key, Object value) { 2 ThreadLocal.ThreadLocalMap.Entry[] tab = table; 3 int len = tab.length; 4 //计算索引(重点代码,刚才分析过了) 5 int i = key.threadLocalHashCode & (len-1); 6 /** 7 * 使用线性探测法查找元素(重点代码) 8 */
9 for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
10 e != null;
11 e = tab[i = nextIndex(i, len)]) {
12 ThreadLocal<?> k = e.get();
13 //ThreadLocal 对应的 key 存在,直接覆盖之前的值
14 if (k == key) {
15 e.value = value;
16 return;
17 }
18 // key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,
19 // 当前数组中的 Entry 是一个陈旧(stale)的元素
20 if (k == null) {
21 //用新元素替换陈旧的元素,这个方法进行了不少的垃圾清理动作,防止内存泄漏
22 replaceStaleEntry(key, value, i);
23 return;
24 }
25 }
26
27 //ThreadLocal对应的key不存在并且没有找到陈旧的元素,则在空元素的位置创建一个新的 Entry。
28 tab[i] = new Entry(key, value);
29 int sz = ++size;
30 /**
31 * cleanSomeSlots用于清除那些e.get()==null的元素,
32 * 这种数据key关联的对象已经被回收,所以这个Entry(table[index])可以被置null。
33 * 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行 * rehash(执行一次全表的扫描清理工作)
34 */
35 if (!cleanSomeSlots(i, sz) && sz >= threshold)
36 rehash();
37 }
38
39 /**
40 * 获取环形数组的下一个索引
41 */
42 private static int nextIndex(int i, int len) {
43 return ((i + 1 < len) ? i + 1 : 0);
44 }
代码执行流程:
A. 首先还是根据key计算出索引 i,然后查找i位置上的Entry,
B. 若是Entry已经存在并且key等于传入的key,那么这时候直接给这个Entry赋新的value值,
C. 若是Entry存在,但是key为null,则调用replaceStaleEntry来更换这个key为空的Entry,
D. 不断循环检测,直到遇到为null的地方,这时候要是还没在循环过程中return,那么就在这个null的
位置新建一个Entry,并且插入,同时size增加1。
最后调用cleanSomeSlots,清理key为null的Entry,最后返回是否清理了Entry,接下来再判断sz 是否
>= thresgold达到了rehash的条件,达到的话就会调用rehash函数执行一次全表的扫描清理。
重点分析 : ThreadLocalMap使用线性探测法来解决哈希冲突的。
该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。
举个例子,假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经
有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]
进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。
按照上面的描述,可以把Entry[] table看成一个环形数组。

浙公网安备 33010602011771号