ThreadLocal 类 的源码解析以及使用原理
正文前先来一波福利推荐:
福利一:
百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。
福利二:
毕业答辩以及工作上各种答辩,平时积累了不少精品PPT,现在共享给大家,大大小小加起来有几千套,总有适合你的一款,很多是网上是下载不到。
获取方式:
微信关注 精品3分钟 ,id为 jingpin3mins,关注后回复 百万年薪架构师 ,精品收藏PPT 获取云盘链接,谢谢大家支持!
-----------------------正文开始---------------------------
1、原理图说明
首先看这一张图,我们可以看出,每一个Thread类中都存在一个属性 ThreadLocalMap 成员,该成员是一个map数据结构,map中是一个Entry的数组,存在entry实体,该实体包含了 key value hash (注意 此map结构不包含next引用 所以不是使用的链地址方法)。
可以是用来存放 ThreadLocal对象以及对应的变量副本;
根据这个原理。我们可以知道在一个线程中可以存储多个 ThreadLocal 对象以及对应的value副本; 所以ThreadLocal 对象的作用就是用来为每一个线程 维护一个 副本;
我们使用ThreadLocal解决线程局部变量统一定义问题,多线程数据不能共享。(InheritableThreadLocal特例除外)不能解决并发问题。解决了:基于类级别的变量定义,每一个线程单独维护自己线程内的变量值(存、取、删的功能)
根据源码,画出原理图如下:
2、源码分析
1.ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法。
2.通过getMap()获取每个子线程Thread持有自己的ThreadLocalMap实例, 因此它们是不存在并发竞争的。可以理解为每个线程有自己的变量副本。
3.ThreadLocalMap中Entry[]数组存储数据,初始化长度16,后续每次都是2倍扩容。主线程中定义了几个变量,Entry[]才有几个key。
4.Entry
的key是对ThreadLocal的弱引用,当ThreadLocal的对象没有被引用时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象, 防止了内存泄漏。
1.1源码注释
下图ThreadId类会在每个线程中生成唯一标识符。线程的id在第一次调用threadid.get()时被分配,在随后的调用中保持不变。
ThreadId类利用AtomicInteger原子方法getAndIncrement,为每个线程创建一个threadId变量,例如第一个线程是1,第二个线程是2...,并提供一个类静态get方法用以获取当前线程ID。:
1 import java.util.concurrent.atomic.AtomicInteger;
2
3 public class ThreadId {
4 // Atomic integer containing the next thread ID to be assigned
5 private static final AtomicInteger nextId = new AtomicInteger(0);
6
7 // Thread local variable containing each thread's ID
8 private static final ThreadLocal<Integer> threadId =
9 new ThreadLocal<Integer>() {
10 @Override protected Integer initialValue() { //为线程产生初始值
11 return nextId.getAndIncrement();
12 }
13 };
14
15 // Returns the current thread's unique ID, assigning it if necessary
16 public static int get() {
17 return threadId.get();
18 }
19 }
如上图,有一个注意点是:用户可以自定义initialValue()初始化方法,来初始化threadLocal的值。
1.2 源码剖析
我们来追踪一下ThreadLocal源码:
1 public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
//Entry 为 ThreadLocal 的静态内部类
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }
//为空时 进行初始化
12 return setInitialValue();
13 }
14
21 private T setInitialValue() {
22 T value = initialValue();
23 Thread t = Thread.currentThread();
24 ThreadLocalMap map = getMap(t);
25 if (map != null)
26 map.set(this, value); //注意次数的this 指的是ThreadLocal对象 也就是说 entry中的键是 ThreadLocal
27 else
28 createMap(t, value);
29 return value;
30 }
31
41 public void set(T value) {
42 Thread t = Thread.currentThread();
43 ThreadLocalMap map = getMap(t);
44 if (map != null)
45 map.set(this, value);
46 else
47 createMap(t, value);
48 }
49
61 public void remove() {
62 ThreadLocalMap m = getMap(Thread.currentThread());
63 if (m != null)
64 m.remove(this);//相当于找到 键 后 删除掉整个Entry 实体
65 }
66
74 ThreadLocalMap getMap(Thread t) {
75 return t.threadLocals;
76 }
看源码我们知道不管是set、get、remove操作的都是ThreadLocalMap,key=ThreadLocal ,value=线程局部变量缓存值。
上图getMap最终调用的Thread的成员变量 ThreadLocal.ThreadLocalMap threadLocals,如下图:
ThreadLocalMap是ThreadLocal的一个内部类,源码注释:
ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocalMap类是包私有的,允许在Thread类中声明字段。为了帮助处理非常大且长时间的使用,哈希表entry使用了对键的弱引用。有助于GC回收。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 分割线 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
散列算法-魔数0x61c88647
ThreadLocal中定义了一个AtomicInteger,一个魔数0x61c88647,利用一定算法实现了元素的完美散列。
源码中元素散列算法如下:
1.求hashCode = i*HASH_INCREMENT+HASH_INCREMENT 每次新增一个元素(threadLocal)进Entry[],自增0x61c88647
2.元素散列位置(数组下标)= hashCode & (length-1),//为什么这样计算 详细看我的另一篇博客 hashmap的原理总结
下面校验算法的散列性:
1 /**
2 *
3 * @ClassName:MagicHashCode
4 * @Description:ThreadLocalMap使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题
7 */
8 public class MagicHashCode {
9 //ThreadLocal中定义的hash魔数
10 private static final int HASH_INCREMENT = 0x61c88647;
11
12 public static void main(String[] args) {
13 hashCode(16);//初始化16
14 hashCode(32);//后续2倍扩容
15 hashCode(64);
16 }
17
18 /**
19 *
20 * @Description 寻找散列下标(对应数组小标)
21 * @param length table长度
22 * @author diandian.zhang
23 * @date 2017年12月6日上午10:36:53
24 * @since JDK1.8
25 */
26 private static void hashCode(Integer length){
27 int hashCode = 0;
28 for(int i=0;i<length;i++){
29 hashCode = i*HASH_INCREMENT+HASH_INCREMENT;//每次递增HASH_INCREMENT
30 System.out.print(hashCode & (length-1));//求散列下标,算法公式
31 System.out.print(" ");
32 }
33 System.out.println();
34 }
35 }
运行结果:
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时,元素完美散列
根据运行结果,代表此算法在长度为2的N次方的数组上,确实可以完美散列。
那么原理是什么?
long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));//(根号5-1)*2的31次方=(根号5-1)/2 *2的32次方=黄金分割数*2的32次方
System.out.println("as 32 bit unsigned: " + l1);//32位无符号整数
int i1 = (int) l1;
System.out.println("as 32 bit signed: " + i1);//32位有符号整数
System.out.println("MAGIC = " + 0x61c88647);
运行结果:
as 32 bit unsigned: 2654435769
as 32 bit signed: -1640531527
MAGIC = 1640531527
这里不再拓展,跟斐波那契数列(和黄金分割数)有关:
1.0x61c88647对应十进制=1640531527。
2.(根号5-1)*2的31次方,转换成long类型就是2654435769,转换成int类型就是-1640531527。
set操作
ThreadLocal的set最终调用了ThreadLocalMap的set方法,如下图
1 private void set(ThreadLocal<?> key, Object value) {
8 Entry[] tab = table;
9 int len = tab.length;
10 int i = key.threadLocalHashCode & (len-1);// 根据
哈希码和数组长度求元素放置的位置,即数组下标
11 //从i开始往后一直遍历到数组最后一个Entry
12 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
15 ThreadLocal<?> k = e.get();
16 //如果key相等,覆盖value
17 if (k == key) {
18 e.value = value;
19 return;
20 }
21 //如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
22 if (k == null) {
23 replaceStaleEntry(key, value, i);
24 return;
25 }
26 }
27
28 tab[i] = new Entry(key, value);
29 int sz = ++size;
//如果超过阀值,就需要再哈希了
30 if (!cleanSomeSlots(i, sz) && sz >= threshold)
31 rehash();
32 }
再哈希:
1 private void rehash() {
2 expungeStaleEntries();// 清理一次陈旧数据 //保证数据及时 GC
3
4 // 清理完陈旧数据,如果>= 3/4阀值,就执行扩容,避免迟滞
5 if (size >= threshold - threshold / 4)
6 resize();
7 }
8
9 /**
10 * 把table扩容2倍,并把老数据重新哈希散列进新table
11 */
12 private void resize() {
13 Entry[] oldTab = table;
14 int oldLen = oldTab.length;
15 int newLen = oldLen * 2;
16 Entry[] newTab = new Entry[newLen];
17 int count = 0;
18 // 遍历Entry[]数组
19 for (int j = 0; j < oldLen; ++j) {
20 Entry e = oldTab[j];
21 if (e != null) {
22 ThreadLocal<?> k = e.get();
23 if (k == null) {// 如果key=null
24 e.value = null; // 把value也置null,有助于GC回收对象
25 } else {// 如果key!=null
26 int h = k.threadLocalHashCode & (newLen - 1);// 计算hash值
27 while (newTab[h] != null)// 如果这个位置已使用
28 h = nextIndex(h, newLen);// 线性往后查询,直到找到一个没有使用的位置,h递增
29 newTab[h] = e;//在第一个空节点上塞入Entry e
30 count++;// 计数++
31 }
32 }
33 }
34
35 setThreshold(newLen);// 设置新的阈值(实际set方法用了2/3的newLen作为阈值)
36 size = count;// 设置ThreadLocalMap的元素个数
37 table = newTab;// 把新table赋值给ThreadLocalMap的Entry[] table
38 }
39
40 /**
41 * 删除陈旧的数据
42 */
43 private void expungeStaleEntries() {
44 Entry[] tab = table;
45 int len = tab.length;
46 for (int j = 0; j < len; j++) {
47 Entry e = tab[j];
48 if (e != null && e.get() == null)//entry不为空且entry的key为null
49 expungeStaleEntry(j);//删除指定数组下标的陈旧entry
50 }
51 }
52 //删除陈旧entry的核心方法
53 private int expungeStaleEntry(int staleSlot) {
54 Entry[] tab = table;
55 int len = tab.length;
56
57
58 tab[staleSlot].value = null;//删除value
59 tab[staleSlot] = null;//删除entry
60 size--;//map的size自减
61
62 // 遍历指定删除节点,所有后续节点
63 Entry e;
64 int i;
65 for (i = nextIndex(staleSlot, len);
66 (e = tab[i]) != null;
67 i = nextIndex(i, len)) {
68 ThreadLocal<?> k = e.get();
69 if (k == null) {//key为null,执行删除操作
70 e.value = null;
71 tab[i] = null;
72 size--;
73 } else {//key不为null,重新计算下标
74 int h = k.threadLocalHashCode & (len - 1);
75 if (h != i) {//如果不在同一个位置
76 tab[i] = null;//把老位置的entry置null(删除)
77
78 // 从h开始往后遍历,一直到找到空为止,插入
80 while (tab[h] != null)
81 h = nextIndex(h, len);
82 tab[h] = e;
83 }
84 }
85 }
86 return i;
87 }
总结set步骤:
1)根据
哈希码和数组长度求元素放置的位置,即数组下标
2)从第一步得出的下标开始往后遍历,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
3)如果超过阀值,就需要再哈希:
- 清理一遍陈旧数据
- >= 3/4阀值,就执行扩容,把table扩容2倍==》注意这里3/4阀值就执行扩容,避免迟滞
- 把老数据重新哈希散列进新table
get操作
1 public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);//从当前线程中获取ThreadLocalMap
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);//查询当前ThreadLocal变量实例对应的Entry
6 if (e != null) {//如果不为null,获取value,返回
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }//如果map为null,即还没有初始化,走初始化方法
12 return setInitialValue();
13 }
14
21 private T setInitialValue() {
22 T value = initialValue();//该方法默认返回null,用户可自定义
23 Thread t = Thread.currentThread();
24 ThreadLocalMap map = getMap(t);
25 if (map != null)//如果map不为null,把初始化value设置进去
26 map.set(this, value);
27 else//如果map为null,则new一个map,并把初始化value设置进去
28 createMap(t, value);
29 return value;
30 }
31
32 void createMap(Thread t, T firstValue) {
33 t.threadLocals = new ThreadLocalMap(this, firstValue);
34 }
35
36 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
37 table = new Entry[INITIAL_CAPACITY];//初始化容量16
38 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
39 table[i] = new Entry(firstKey, firstValue);
40 size = 1;
41 setThreshold(INITIAL_CAPACITY);//设置阈值
42 }
43 //阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希
44 private void setThreshold(int len) {
45 threshold = len * 2 / 3;
46 }
总结get步骤:
1)从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,返回
2)如果map为null,即还没有初始化,走初始化方法
remove操作
1 public void remove() {
2 ThreadLocalMap m = getMap(Thread.currentThread());
3 if (m != null)
4 m.remove(this);//调用ThreadLocalMap删除变量
5 }
6
7 private void remove(ThreadLocal<?> key) {
8 Entry[] tab = table;
9 int len = tab.length;
10 int i = key.threadLocalHashCode & (len-1);
11 for (Entry e = tab[i];
12 e != null;
13 e = tab[i = nextIndex(i, len)]) {
14 if (e.get() == key) {
15 e.clear();//调用Entry的clear方法
16 expungeStaleEntry(i);//清除陈旧数据
17 return;
18 }
19 }
20 }
看一下Entry的clear方法,Entry ==extends==》 WeakReference<ThreadLocal<?>>==extends==》 Reference<T>,clear方法是抽象类Reference定义的方法。
1 static class Entry extends WeakReference<ThreadLocal<?>> {
2 /** The value associated with this ThreadLocal. */
3 Object value;
4
5 Entry(ThreadLocal<?> k, Object v) {
6 super(k);
7 value = v;
8 }
9 }
追一下clear方法如下:把弱引用的对象置null。有利于GC回收内存。关于引用,预留飞机票
public void clear() {
this.referent = null;
}
1.3 功能测试
开启2个线程,每个个线程都使用类级别的threadLocal,往里面递增数字,i=0,时,set(0),i=1,2,3时 值+1,
1 /**
2 *
3 * @ClassName:MyThreadLocal
4 * @Description:ThreadLocal线程本地变量
5 * @author diandian.zhang
6 * @date 2017年12月4日上午9:40:52
7 */
8 public class MyThreadLocal{
9 //线程本地共享变量
10 private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
11 /**
12 * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
13 */
14 @Override
15 protected Object initialValue()
16 {
17 System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
18 return null;
19 }
20 };
21
22 public static void main(String[] args){
23 //1.开启任务1线程
24 new Thread(new MyIntegerTask("IntegerTask1")).start();
25 //2.中间休息3秒,用以测试数据差异
26 try {
27 Thread.sleep(3000);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 //3.开启任务2线程
32 new Thread(new MyIntegerTask("IntegerTask2")).start();
33 }
34
35 /**
36 *
37 * @ClassName:MyIntegerTask
38 * @Description:整形递增线程
39 * @author diandian.zhang
40 * @date 2017年12月4日上午10:00:41
41 */
42 public static class MyIntegerTask implements Runnable{
43 private String name;
44
45 MyIntegerTask(String name)
46 {
47 this.name = name;
48 }
49
50 @Override
51 public void run()
52 {
53 for(int i = 0; i < 5; i++)
54 {
55 // ThreadLocal.get方法获取线程变量
56 if(null == MyThreadLocal.threadLocal.get())
57 {
58 // ThreadLocal.set方法设置线程变量
59 MyThreadLocal.threadLocal.set(0);
60 System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
61 }
62 else
63 {
64 int num = (Integer)MyThreadLocal.threadLocal.get();
65 MyThreadLocal.threadLocal.set(num + 1);
66 System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyThreadLocal.threadLocal.get());
67 //当i=3即循环4次时,移除当前线程key
68 if(i == 3)
69 {
70 System.out.println("i="+i+"[线程" + name + "],threadLocal移除当前线程" );
71 MyThreadLocal.threadLocal.remove();
72 }
73 }
74 try
75 {
76 Thread.sleep(1000);
77 }
78 catch (InterruptedException e)
79 {
80 e.printStackTrace();
81 }
82 }
83 }
84 }
85 }
运行结果如下:
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=0[线程IntegerTask1]当前线程不存在缓存,set 0
i=1[线程IntegerTask1]往threadLocal中set: 1
i=2[线程IntegerTask1]往threadLocal中set: 2
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=0[线程IntegerTask2]当前线程不存在缓存,set 0
i=3[线程IntegerTask1]往threadLocal中set: 3
i=3[线程IntegerTask1],threadLocal移除当前线程
i=1[线程IntegerTask2]往threadLocal中set: 1
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]往threadLocal中set: 2
i=3[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask2],threadLocal移除当前线程
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0
结果验证:
1.2个线程,2个threadLocal变量互不影响。
2.调用get方法时,对应ThreadLocalMap为null会调用initialValue()方法,初始化threadLocal的值。
1.4 应用场景
ThreadLocal的实际应用场景:
1)数据结构:用Map<String, Object>来避免创建多个ThreadLocal变量的麻烦。只需根据map的key就可以获取想要的value
private static final ThreadLocal<Map<String, Object>> loginContext = new ThreadLocal<>();
2)业务:线程级别,维护session,维护用户登录信息userID(登陆时插入,多个地方获取)等,尤其适合使用在WEB项目中(Tomcat容器,工作线程隔离)
二、变量可继承的ThreadLocal==》InheritableThreadLocal
2.1 源码注释:
这个类扩展ThreadLocal,以提供从父线程到子线程的值的继承:当创建子线程时,子线程会接收父元素所具有值的所有可继承线程局部变量的初始值。正常情况下,子线程的变量值与父线程的相同;然而,子线程可复写childValue方法来自定义获取父类变量。
当变量(例如,用户ID、事务ID)中维护的每个线程属性必须自动传输到创建的任何子线程时,使用InheritableThreadLocal优于ThreadLocal。
2.2 源码剖析
1.子线程启动时,调用init方法,如果父线程有InheritableThreadLocal变量,则在子线程也生成一份
下图是Thread类在init时执行的逻辑:
调用createInheritedMap方法,并调用childValue方法复制一份变量给子线程
1 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
2 return new ThreadLocalMap(parentMap);
3 }
4
5 private ThreadLocalMap(ThreadLocalMap parentMap) {
6 Entry[] parentTable = parentMap.table;
7 int len = parentTable.length;
8 setThreshold(len);
9 table = new Entry[len];
10
11 for (int j = 0; j < len; j++) {
12 Entry e = parentTable[j];
13 if (e != null) {
14 @SuppressWarnings("unchecked")
15 ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
16 if (key != null) {
17 Object value = key.childValue(e.value);
18 Entry c = new Entry(key, value);
19 int h = key.threadLocalHashCode & (len - 1);
20 while (table[h] != null)
21 h = nextIndex(h, len);
22 table[h] = c;
23 size++;
24 }
25 }
26 }
27 }
2.支持用户自定义childValue函数,用以子类获取父类变量值的转换:父类变量----childValue转换函数-----》子类变量
InheritableThreadLocal默认childValue函数是直接返回:
protected T childValue(T parentValue) {
return parentValue;
}
用户可在创建InheritableThreadLocal变量时,覆盖childValue函数,见3.3测试
2.3 功能测试
1 package threadLocal;
2
3
4 /**
5 *
6 * @ClassName:MyInheritableThreadLocal
7 * @Description:可继承线程本地变量
8 * @author denny.zhang
9 * @date 2017年12月7日下午5:24:40
10 */
11 public class MyInheritableThreadLocal{
12 //线程本地共享变量
13 private static final InheritableThreadLocal<Object> threadLocal = new InheritableThreadLocal<Object>(){
14 /**
15 * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
16 */
17 @Override
18 protected Object initialValue()
19 {
20 System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
21 return null;
22 }
23
24 @Override
25 protected Object childValue(Object parentValue) {
26 return (Integer)parentValue*2;
27 }
28
29 };
30
31 public static void main(String[] args){
32 //主线程设置1
33 threadLocal.set(1);
34 //1.开启任务1线程
35 new Thread(new MyIntegerTask("IntegerTask1")).start();
36 //2.中间休息3秒,用以测试数据差异
37 try {
38 Thread.sleep(3000);
39 } catch (InterruptedException e) {
40 e.printStackTrace();
41 }
42 //开启任务2线程
43 new Thread(new MyIntegerTask("IntegerTask2")).start();
44 }
45
46 /**
47 *
48 * @ClassName:MyIntegerTask
49 * @Description:整形递增线程
50 * @author diandian.zhang
51 * @date 2017年12月4日上午10:00:41
52 */
53 public static class MyIntegerTask implements Runnable{
54 private String name;
55
56 MyIntegerTask(String name)
57 {
58 this.name = name;
59 }
60
61 @Override
62 public void run()
63 {
64 for(int i = 0; i < 5; i++)
65 {
66 // ThreadLocal.get方法获取线程变量
67 if(null == MyInheritableThreadLocal.threadLocal.get())
68 {
69 // ThreadLocal.set方法设置线程变量
70 MyInheritableThreadLocal.threadLocal.set(0);
71 System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
72 }
73 else
74 {
75 int num = (Integer)MyInheritableThreadLocal.threadLocal.get();
76 System.out.println("i="+i+"[线程" + name + "]get=" + num);
77 MyInheritableThreadLocal.threadLocal.set(num + 1);
78 System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get());
79 //当i=3即循环4次时,移除当前线程key
80 if(i == 3)
81 {
82 System.out.println("i="+i+"[线程" + name + "],remove" );
83 MyInheritableThreadLocal.threadLocal.remove();
84 }
85 }
86 try
87 {
88 Thread.sleep(1000);
89 }
90 catch (InterruptedException e)
91 {
92 e.printStackTrace();
93 }
94 }
95 }
96 }
97 }
运行结果:
主线程变量值=1-----》主线程中变量值1
i=0[线程IntegerTask1]get=2-----》子线程1中变量值=2*1=2,验证通过!
i=0[线程IntegerTask1]往threadLocal中set: 3
i=1[线程IntegerTask1]get=3
i=1[线程IntegerTask1]往threadLocal中set: 4
i=2[线程IntegerTask1]get=4
i=2[线程IntegerTask1]往threadLocal中set: 5
i=0[线程IntegerTask2]get=2-----》主线程2中变量值=2*1=2,验证通过!
i=0[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask1]get=5
i=3[线程IntegerTask1]往threadLocal中set: 6
i=3[线程IntegerTask1],remove
i=1[线程IntegerTask2]get=3
i=1[线程IntegerTask2]往threadLocal中set: 4
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]get=4
i=2[线程IntegerTask2]往threadLocal中set: 5
i=3[线程IntegerTask2]get=5
i=3[线程IntegerTask2]往threadLocal中set: 6
i=3[线程IntegerTask2],remove
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0
如上图,分析结果我们可知,
1.子线程根据childValue函数获取到了父线程的变量值。
2.多线程InheritableThreadLocal变量各自维护,无竞争关系。
2.4 应用场景
子线程变量数据依赖父线程变量,且自定义赋值函数。
例如:
开启多线程执行任务时,总任务名称叫mainTask 子任务名称依次递增mainTask-subTask1、mainTask-subTask2、mainTask-subTaskN等等
三、总结
本文分析了ThreadLocal原理、set(散列算法原理和测试验证,再哈希扩容)、get、remove源码,实际中的应用场景以及功能测试验证。最后又分析了InheritableThreadLocal,使用该类子线程会继承父线程变量,并自定义赋值函数。
读完本文,相信大家对ThreadLocal一点也不担心了哈哈!
需要注意2点:
1.ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争!目的是线程本地变量且只能单个线程内维护使用。
2.InheritableThreadLocal对比ThreadLocal唯一不同是子线程会继承父线程变量,并自定义赋值函数。
3.项目如果使用了线程池,那么小心线程回收后ThreadLocal、InheritableThreadLocal变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写afterCompletion方法,remove掉变量!!!)
ThreadLocal 类 的源码解析以及使用原理的更多相关文章
- 线程池 ThreadPoolExecutor 类的源码解析
线程池 ThreadPoolExecutor 类的源码解析: 1:数据结构的分析: private final BlockingQueue<Runnable> workQueue; // ...
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...
- 多线程爬坑之路-J.U.C.atomic包下的AtomicInteger,AtomicLong等类的源码解析
Atomic原子类:为基本类型的封装类Boolean,Integer,Long,对象引用等提供原子操作. 一.Atomic包下的所有类如下表: 类摘要 AtomicBoolean 可以用原子方式更新的 ...
- DRF之视图类(mixin)源码解析
同样的增删改查操作,如果我们还像之前序列化组件那样做,代码重复率过多,所以我们用视图表示: 具体源码实现:首先定义一个视图类,然后根据mixin点进去有五个封装好的方法,这五个方法共有的属性就是都需 ...
- [Java多线程]-J.U.C.atomic包下的AtomicInteger,AtomicLong等类的源码解析
Atomic原子类:为基本类型的封装类Boolean,Integer,Long,对象引用等提供原子操作. 一.Atomic包下的所有类如下表: 类摘要 AtomicBoolean 可以用原子方式更新的 ...
- spring源码解析之AOP原理
一.准备工作 在这里我先简单记录下如何实现一个aop: AOP:[动态代理] 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式: 1.导入aop模块:Spring AOP:(s ...
- 2、Dubbo源码解析--服务发布原理(Netty服务暴露)
一.服务发布 - 原理: 首先看Dubbo日志,截取重要部分: 1)暴露本地服务 Export dubbo service com.alibaba.dubbo.demo.DemoService to ...
- 透过 NestedScrollView 源码解析嵌套滑动原理
NestedScrollView 是用于替代 ScrollView 来解决嵌套滑动过程中的滑动事件的冲突.作为开发者,你会发现很多地方会用到嵌套滑动的逻辑,比如下拉刷新页面,京东或者淘宝的各种商品页面 ...
- Netty源码解析 -- PoolChunk实现原理
本文主要分享Netty中PoolChunk如何管理内存. 源码分析基于Netty 4.1.52 内存管理算法 首先说明PoolChunk内存组织方式. PoolChunk的内存大小默认是16M,Net ...
随机推荐
- python读取并写入csv文件
在ubuntu下,新建.csv文件的方法是使用LibreOffice来创建一个数据表,然后我们把表格存储为.csv的格式: “Save as”菜单把我们的表格存为一个CSV的文件格式:命名为csvDa ...
- openfire的SSL双向认证增加android客户端证书库步骤
过程 需要新制作PKCS12证书库.CER证书.转换为androidBKS证书,最后把客户端的CER证书导入进im服务器的私钥库client.truststore,然后替换原证书. 新证书生成步骤 ...
- VueJs(1)---快速上手VueJs
[VueJs入门] 版权声明 首先申明:此篇博客不是本人原创,只是最近开始学习vue.jS,看到有作者写的很不错,我仅在它的基础上仅仅是修改了样式 原文博客地址:https://blog.csdn.n ...
- 关于 Kubernetes 中的 Volume 与 GlusterFS 分布式存储
容器中持久化的文件生命周期是短暂的,如果容器中程序崩溃宕机,kubelet 就会重新启动,容器中的文件将会丢失,所以对于有状态的应用容器中持久化存储是至关重要的一个环节:另外很多时候一个 Pod 中可 ...
- mysql 开发基础系列12 选择合适的数据类型(上)
一. char 与varchar比较 在上图的最后一行的值只适用在"非严格模式",关于严格模式后面讲到.在“开发基础系列4“ 中讲到CHAR 列删除了尾部的空格.由于char是固定 ...
- java--uploadify3.1多文件上传
使用uploadify时,建议下载uploadify3.1文档.边做边看. 这是页面端: <label style="color:#15428B;font-weight:bold;&q ...
- 【原创】驱动加载之StartService
BOOL WINAPI StartService( _In_ SC_HANDLE hService, _In_ DWORD dwNumServiceArgs, _In_opt_ LPCTSTR *lp ...
- java ReentrantLock
介绍 ReentrantLock称为重入锁,比内部锁synchonized拥有更强大的功能,它可中断.可定时.设置公平锁 [注]使用ReentrantLock时,一定要释放锁,一般释放放到finnal ...
- zookeeper配置中心实战--solrcloud zookeeper配置中心原理及源码分析
程序的发展,需要引入集中配置: 随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关.参数的配置.服务器的地址…… 并且对配置的期望也越来越高,配置修改后实时生效,灰度发布,分环境.分集群管理配 ...
- 垂直居中—3行CSS3代码
方法一: .element { position: relative; top: 50%; transform: translateY(-50%); } 这用用的好处了,无论是块级元素还是行内元素,都 ...