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,对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干 扰。

image

 

 

三 内存泄漏

待整理

四 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看成一个环形数组

 

posted @ 2026-04-08 16:10  OMGq  阅读(24)  评论(0)    收藏  举报