一、ThreadLocal简介

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
  
也就是说:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本

二、threadlocal的简单使用

开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,(不然会造成内存泄漏)代码如下所示:

1 package test;2 3 public class ThreadLocalTest {4 5     static ThreadLocal<String> localVar = new ThreadLocal<>();6 7     static void print(String str) {8         //打印当前线程中本地内存中本地变量的值9         System.out.println(str + " :" + localVar.get());
10         //清除本地内存中的本地变量
11         localVar.remove();
12     }
13
14     public static void main(String[] args) {15         Thread t1  = new Thread(new Runnable() {16             @Override
17             public void run() {18                 //设置线程1中本地变量的值
19                 localVar.set("localVar1");
20                 //调用打印方法
21                 print("thread1");
22                 //打印本地变量
23                 System.out.println("after remove : " + localVar.get());
24             }
25         });
26
27         Thread t2  = new Thread(new Runnable() {28             @Override
29             public void run() {30                 //设置线程1中本地变量的值
31                 localVar.set("localVar2");
32                 //调用打印方法
33                 print("thread2");
34                 //打印本地变量
35                 System.out.println("after remove : " + localVar.get());
36             }
37         });
38
39         t1.start();
40         t2.start();
41     }
42 }

三、ThreadLocal的实现原理

从ThreadLocal的结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的:

1.set方法

1 public void set(T value) {2     //(1)获取当前线程(调用者线程)3     Thread t = Thread.currentThread();4     //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map5     ThreadLocalMap map = getMap(t);6     //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值7     if (map != null)8         map.set(this, value);9     //(4)如果map为null,说明首次添加,需要首先创建出对应的map
10     else
11         createMap(t, value);
12 }

2.get方法

1 public T get() {2     //(1)获取当前线程3     Thread t = Thread.currentThread();4     //(2)获取当前线程的threadLocals变量5     ThreadLocalMap map = getMap(t);6     //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值7     if (map != null) {8         ThreadLocalMap.Entry e = map.getEntry(this);9         if (e != null) {10             @SuppressWarnings("unchecked")
11             T result = (T)e.value;
12             return result;
13         }
14     }
15     //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
16     return setInitialValue();
17 }
18
19 private T setInitialValue() {20     //protected T initialValue() {return null;}
21     T value = initialValue();
22     //获取当前线程
23     Thread t = Thread.currentThread();
24     //以当前线程作为key值,去查找对应的线程变量,找到对应的map
25     ThreadLocalMap map = getMap(t);
26     //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
27     if (map != null)
28         map.set(this, value);
29     //如果map为null,说明首次添加,需要首先创建出对应的map
30     else
31         createMap(t, value);
32     return value;
33 }

3.remove方法

1  public void remove() {2     //获取当前线程绑定的threadLocals
3      ThreadLocalMap m = getMap(Thread.currentThread());
4      //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
5      if (m != null)
6          m.remove(this);
7  }

四、能否继承

1.ThreadLocal类不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

1 package test;2 3 public class ThreadLocalTest2 {4 5     //(1)创建ThreadLocal变量6     public static ThreadLocal<String> threadLocal = new ThreadLocal<>();7 8     public static void main(String[] args) {9         //在main线程中添加main线程的本地变量
10         threadLocal.set("mainVal");
11         //新创建一个子线程
12         Thread thread = new Thread(new Runnable() {13             @Override
14             public void run() {15                 System.out.println("子线程中的本地变量值:"+threadLocal.get());
16             }
17         });
18         thread.start();
19         //输出main线程中的本地变量值
20         System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
21     }
22 }

2.InheritableThreadLocal类

在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

1 public class InheritableThreadLocal<T> extends ThreadLocal<T> {2     3     protected T childValue(T parentValue) {4         return parentValue;5     }6 7     ThreadLocalMap getMap(Thread t) {8        return t.inheritableThreadLocals;9     }
10
11     void createMap(Thread t, T firstValue) {12         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
13     }
14 }

从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中createMap方法在被调用(当前线程调用set方法时得到的map为null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是threadLocals。同理,getMap方法在当前调用者线程调用get方法的时候返回的也不是threadLocals而是inheritableThreadLocal。

五、总结

ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。
THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。

应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

threallocal详解相关推荐

  1. 史上最全ThreadLocal 详解(一)

    目录 一.ThreadLocal简介 二.ThreadLocal与Synchronized的区别 三.ThreadLocal的简单使用 四.ThreadLocal的原理 4.1 ThreadLocal ...

  2. 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)

    首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...

  3. JVM年轻代,老年代,永久代详解​​​​​​​

    秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...

  4. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  5. 通俗易懂word2vec详解词嵌入-深度学习

    https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...

  6. 深度学习优化函数详解(5)-- Nesterov accelerated gradient (NAG) 优化算法

    深度学习优化函数详解系列目录 深度学习优化函数详解(0)– 线性回归问题 深度学习优化函数详解(1)– Gradient Descent 梯度下降法 深度学习优化函数详解(2)– SGD 随机梯度下降 ...

  7. CUDA之nvidia-smi命令详解---gpu

    nvidia-smi是用来查看GPU使用情况的.我常用这个命令判断哪几块GPU空闲,但是最近的GPU使用状态让我很困惑,于是把nvidia-smi命令显示的GPU使用表中各个内容的具体含义解释一下. ...

  8. Bert代码详解(一)重点详细

    这是bert的pytorch版本(与tensorflow一样的,这个更简单些,这个看懂了,tf也能看懂),地址:https://github.com/huggingface/pytorch-pretr ...

  9. CRF(条件随机场)与Viterbi(维特比)算法原理详解

    摘自:https://mp.weixin.qq.com/s/GXbFxlExDtjtQe-OPwfokA https://www.cnblogs.com/zhibei/p/9391014.html C ...

最新文章

  1. 《Enterprise Library深入解析与灵活应用》博文系列汇总
  2. python中循环结构分有,python常见循环结构有哪些
  3. Sagemaker快速学习
  4. SAP Fiori Elements - how to create annotation for a property
  5. 的函数原型_JS基础函数、对象和原型、原型链的关系
  6. 四步获取微信登录所需的openid和session_key
  7. Python中通过cx_Oracle访问数据库遇到的问题总结
  8. java线程释放_Java多线程出现异常会自动释放锁
  9. 在Windows系统中用nginx与mono搭建asp.net运行环境,附详细例图与代码
  10. 重启路由器可以换IP吗
  11. vue报错RangeError: Maximum call stack size exceeded
  12. 通过VBA将excel数据导入至word文档
  13. Vue--改变鼠标指针的图片
  14. CodeForces 379A. New Year Candles
  15. 爬取豆瓣音乐Top250详细教程
  16. phpadmin的安装教程
  17. Android多进程从头讲到尾,成功定级腾讯T3-2
  18. 中国热泵热水器行业发展趋势及投资风险研究报告
  19. 时间序列分析这件小事(八)----格兰杰因果关系检验
  20. dcloud 5+ 监听安卓前后台切换状态 并后台运行程序

热门文章

  1. java .equal_Java中的equals()
  2. 一年级下册数学计算机应用题,一年级数学下册期中检测试题
  3. linux内存管理机制
  4. ant编译无法依赖rt.jar
  5. 省选专练【POI2015】Podzial naszyjnika
  6. 洛谷P1852 奇怪的字符串
  7. 在css/js代码上线之后开发人员经常会优化性能,从用户刷新网页开始,一次js请求一般情况下有哪些地方会有缓存处理?...
  8. linux 下安装chrome的rpm包
  9. JNA参数传递问题,Java数组
  10. 面向对象风格的合理架构