在学习ThreadLocal之前,建议先了解Java中的4种引用

一、先看一下Thread,ThreadMap,ThreadLocal的关系

Thread中持有一个ThreadLocalMap ,这里你可以简单理解为就是持有一个数组,这个数组是Entry 类型的。 Entry 的key 是ThreadLocal 类型的(准确说是一个指向ThreadLocal的引用)value 是Object 类型。也就是一个ThreadLocalMap 可以持有多个ThreadLocal。他们是一对多的关系

同时通过源码我们可以看到,ThreadLocalMap是ThreadLocal里的一个静态内部类,Entry又是ThreadLocalMap的一个静态内部类。这里可以思考一下为何不直接使用HashMap?(HashMap节点之间是通过强引用关联)

这里明明确一点:ThreadLocal不是隶属于Thread的,他是一个独立的(一个用于操作Thread中threadLocals属性的工具类),使用他的时候,必须new出来,而不是通过Thread来获得,明确这点,我感觉理解起来会好很多。

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;//可以看到Thread的threadLocals属性底层是一个//由ThreadLocal类作为K,Object类作为v的Map结构Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
  • ThreadLocal的作用:为共享变量在每一个线程中创建一个副本,以保证不同的线程中拿到该变量的值都是不一样的

二、ThreadLocal的基本方法

在实际使用之前我们先来了解一下ThreadLocal的几个常用方法void

  • set(Object value)设置当前线程的线程局部变量的值。
public void set(T value) {Thread t = Thread.currentThread();//获取当前执行的线程ThreadLocalMap map = getMap(t); //获得当前线程的ThreadLocalMap实例if (map != null)//如果map不为空,说明当前线程已经有了一个ThreadLocalMap实例//注意这个方法,底层会覆盖同一个k的v,也就是每一个Thread中的某个ThreaLocal的可以重复设置,但会覆盖前面的值map.set(this, value);//直接将当前value设置到ThreadLocalMap中elsecreateMap(t, value); //说明当前线程是第一次使用线程本地变量,构造map
}
  • 该方法返回当前线程所对应的线程局部变量。
 public T get() {Thread t = Thread.currentThread();//获得当前线程的ThreadLocalMap实例ThreadLocalMap map = getMap(t);if (map != null) {//查询当前ThreadLocal变量实例对应的EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//返回对应Entry的valueT result = (T)e.value;return result;}}//如果map为null,即还没有初始化,走初始化方法return setInitialValue();}
--------------------------------------------------------private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.refersTo(key))return e;elsereturn getEntryAfterMiss(key, i, e);}
-----------------------------------------------------------------
private T setInitialValue() {T value = initialValue();//protected方法,用户可以重写Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null){//如果map不为null,把初始化value设置进去map.set(this, value);}else{//如果map为null,则new一个map,并把初始化value设置进去createMap(t, value);}if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;
}
  • public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {//根据当前ThreadLocal变量删除Thread.ThreadLocals中对应的值m.remove(this);}}
  • protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

三、引用ThreadLocal时示意图

代码:

/*** @author Cristianoxm*/
public class TestThreadLocal
{static ThreadLocal<String> threadLocal1=new ThreadLocal<String>();static ThreadLocal<Integer> threadLocal2=new ThreadLocal<Integer>();public static void main(String[] args) {Thread [] runs=new Thread[3];for (int i=0;i<runs.length;i++){new MyThread(i).start();}}public static class MyThread extends Thread{int id;public MyThread(int id){this.id=id;}@Overridesynchronized public void run(){threadLocal1.set("线程-"+id);if(id == 1){threadLocal2.set(1);}System.out.println(Thread.currentThread().getName()+"编号:"+threadLocal1.get());System.out.println(threadLocal2.get());}}
}

内存图大概如下:只有Thread-1中有指向Threadlocal2的Entry,thread-2中是没有的

四、底层原理解析

掌握了基本用法之后,我们就来聊聊ThreadLocal 的底层原理吧。在java中每一个线程都会有一个 ThrealLocalMap ,里面存放了一个一个的键值对(我们一般称之为Entry),键值对的key 就是 Threadlocal本身,而value 就是他的值,正如下图所示。

我们仔细的看一下这张图。左边是stack(栈),栈里面有用户线程和threadlocal的引用。而在堆里,threadlocal 用一个弱引用指向了我们ThrealLocalMap 的key,而用户线程 则是一个强引用指向了 这个enrty的 value.现在我们就能理解为什么threadlocal 的副本是怎么回事了,线程拷贝一份值,用Threadlocal自身做key ,保存在一个map中,从而实现了每个线程之间变量的互相隔离

五、内存泄露分析

提到Threadlocal 必然不得不说他的内存泄露问题,首先我们注意下,threadlocalmap 是一个弱引用,也就是说只要gc,我们上面这条 ThreadLocal->key 的虚线就回被打断,但是如果我们的当前线程没有结束,那么指向value的强引用自然不会断开。于是当gc发生的时候,我们的map中就出现了一个,key为null的 entry,但是这个entry 由于value的强引用并不会被回收,那么map中就出现了一个永远访问不到的entry!内存泄漏由此产生。当然,threadLocal 对此还是有对策的,当我们调用 get set 和 remove 方法的时候,threadlocal都会去检查是否有这样的entry并把他们清除掉。但是注意,除了 remove方法,其他两个方法都是不可靠的,他们并非及时同时不一定会去执行清除方法。

  • 对象引用图:

所以为了防止内存泄露,我们必须在使用完Threadlocal后及时的去调用remove方法!所以:ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。想要避免内存泄露就要手动remove()掉!

  • 那么:为什么使用弱引用

说道这里可能很多人会问了,为什么threadlocal要采用弱引用,采用强引用不是就没有这个访问不到entry的毛病了吗。那我们这里就假设一下,如果这里的key是强引用的话,如果我们给这个Threadlocal 赋值为null。那么情况就如下图所示

请大家注意这里 指向我们value的线已经断了,照理来说这个entry应该被回收,但是由于我们的key是强引用,所以这个entry就变得和 Threadlocal同生共死了,从而造成内存泄露!也即是:entry中的value置空,threadlocal置空,那么这个空的entry会因为key的强引用,变得和 Threadlocal同生共死,就内存泄露了

  • 总结使用ThreadLocal时会发生内存泄漏的前提条件:
  • ①ThreadLocal引用被设置为null,且后面没有set,get,remove操作。
  • ②线程一直运行,不停止。(线程池)
  • ③触发了垃圾回收。(Minor GC或Full GC)

我们看到ThreadLocal出现内存泄漏条件还是很苛刻的,所以我们只要破坏其中一个条件就可以避免内存泄漏,单但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则

  • ThreadLocal申明为private static final。
    Private与final 尽可能不让他人修改变更引用,
    Static 表示为类属性,只有在程序结束才会被回收。
  • ThreadLocal使用后务必调用remove方法
    最简单有效的方法是使用后将其移除。

六、流程图

七、ThreadLocal的应用

  • 管理Connection
    最典型的是管理数据库的Connection:当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池,那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢??ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务
public class DBUtil {//数据库连接池private static BasicDataSource source;//为不同的线程管理连接private static ThreadLocal<Connection> local;static {try {//加载配置文件Properties properties = new Properties();//获取读取流InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("连接池/config.properties");//从配置文件中读取数据properties.load(stream);//关闭流stream.close();//初始化连接池source = new BasicDataSource();//设置驱动source.setDriverClassName(properties.getProperty("driver"));//设置urlsource.setUrl(properties.getProperty("url"));//设置用户名source.setUsername(properties.getProperty("user"));//设置密码source.setPassword(properties.getProperty("pwd"));//设置初始连接数量source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));//设置最大的连接数量source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));//设置最长的等待时间source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));//设置最小空闲数source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));//初始化线程本地local = new ThreadLocal<>();} catch (IOException e) {e.printStackTrace();}}public static Connection getConnection() throws SQLException {if(local.get()!=null){return local.get();}else{//获取Connection对象Connection connection = source.getConnection();//把Connection放进ThreadLocal里面local.set(connection);//返回Connection对象return connection;}}//关闭数据库连接public static void closeConnection() {//从线程中拿到Connection对象Connection connection = local.get();try {if (connection != null) {//恢复连接为自动提交connection.setAutoCommit(true);//这里不是真的把连接关了,只是将该连接归还给连接池connection.close();//既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了local.remove();}} catch (SQLException e) {e.printStackTrace();}}
}

同样的,Hibernate对Connection的管理也是采用了相同的手法(使用ThreadLocal,当然了Hibernate的实现是更强大的)

八、InheritableThreadLocal

InheritableThreadLocal类其实是重写了ThreadLocal的3个函数:

     /*** 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用*/protected T childValue(T parentValue) {return parentValue;}/*** 由于重写了getMap,操作InheritableThreadLocal时,* 将只影响Thread类中的inheritableThreadLocals变量,* 与threadLocals变量不再有关系*/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}/*** 类似于getMap,操作InheritableThreadLocal时,* 将只影响Thread类中的inheritableThreadLocals变量,* 与threadLocals变量不再有关系*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
  • 作用:我们知道:当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal。而InheritableThreadLocal,是自父线程集成而来的ThreadLocalMap,主要用于父子线程间ThreadLocal变量的传递。 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap

九、inheritThreadLocals的使用

  • thread的创建
Thread thread = new Thread();
----------------------------------------------
public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}
 /*** 默认情况下,设置inheritThreadLocals可传递*/private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);}
 /*** 初始化一个线程.* 此函数有两处调用,* 1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true* 2、传递AccessControlContext,inheritThreadLocals=false*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {......(其他代码)if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);......(其他代码)}

可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}
        /*** 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap,该函数只被 createInheritedMap() 调用.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);// ThreadLocalMap 使用 Entry[] table 存储ThreadLocaltable = new Entry[len];// 逐一复制 parentMap 的记录for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {// 可能会有同学好奇此处为何使用childValue,而不是直接赋值,// 毕竟childValue内部也是直接将e.value返回;// 个人理解,主要为了减轻阅读代码的难度Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

从ThreadLocalMap可知,子线程将parentMap中的所有记录逐一复制至自身线程。

  • 代码例子
public class TestInheritableThreadLocal {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static String get() {return threadLocal.get();}public static void set(String value) {threadLocal.set(value);}public static String inheritableThreadLocalGet() {return inheritableThreadLocal.get();}public static void inheritableThreadLocalSet(String value) {inheritableThreadLocal.set(value);}public static void main(String[] args) {for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.set("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.get()));t.start();}for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.inheritableThreadLocalSet("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.inheritableThreadLocalGet()));t.start();}}
}

参考文章
参考文章

ThreadLocal 和 InheritableThreadLocal相关推荐

  1. 每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal

    文章目录 ThreadLocal 核心API ThreadLocal类 源码分析 set get remove 缺陷 InheritableThreadLocal 源码解析 局限性 Transmitt ...

  2. 【高并发】ThreadLocal、InheritableThreadLocal

    文章目录 1.概述 2.需要解决的问题 ThreadLocal InheritableThreadLocal 1.概述 转载:添加链接描述 2.需要解决的问题 我们还是以解决问题的方式来引出Threa ...

  3. ThreadLocal和InheritableThreadLocal使用

    InheritableThreadLocal代码 public class InheritableThreadLocal<T> extends ThreadLocal<T> { ...

  4. 多线程-ThreadLocal,InheritableThreadLocal

    ThreadLocal 变量值得共享可以使用public static变量的形式,所有的线程都使用同一个public static变量.如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供 ...

  5. 【多线程编程】--ThreadLocal、InheritableThreadLocal(ITL)、TransmittableThreadLocal(TTL)解析

    目录 一.前言 二.ThreadLocal 2.1.为什么会用到ThreadLocal(ThreadLocal应用场景) 2.2.ThreadLocal实现原理 2.3.ThreadLocalMap ...

  6. ThreadLocal与InheritableThreadLocal源码解析

    ThreadLocal 变量值的共享可以使用public static变量的形式实现,所有的线程都使用同一个public static变量,那如何实现每一个线程都有自己的变量呢?JDK提供的Threa ...

  7. JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践

    本文是JUC第六讲:ThreadLocal/InheritableThreadLocal详解.ThreadLocal无论在项目开发还是面试中都会经常碰到,本文就 ThreadLocal 的使用.主要方 ...

  8. 【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

    来自:冰河技术 前言 我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示. 对共享变量加锁虽 ...

  9. 获取返回值作为变量_解决多线程间共享变量线程安全问题的大杀器——ThreadLocal...

    微信公众号:Zhongger 我是Zhongger,一个在互联网行业摸鱼写代码的打工人! 关注我,了解更多你不知道的[Java后端]打工技巧.职场经验等- 上一期,讲到了关于线程死锁.用户进程.用户线 ...

最新文章

  1. The Long-Term Stability of Ecosystems
  2. Gym 101334A Area 51 数学
  3. FFmpeg--命令详解
  4. 【转载】正则表达式30分钟入门教程
  5. Python中的pip包管理工具被删除重新进行安装
  6. 用Python+Appium自动写网课考试
  7. Superset 实现可视化报表发布
  8. 在Word文档里如何快速返回目录页-Office学习
  9. 一款完整开源的物联网基础平台
  10. A12 屏幕旋转流程
  11. 论文中的 w.r.t. 和 i.e. 是什么意思
  12. Zoreto+坚果云+pdf expert(papership) 实现文献管理和批注同步
  13. 高德导航在天地图显示
  14. 新手建网站的步骤及注意事项
  15. 基于时间序列分析方法的零售业快消品销量预测研究
  16. Automated Phrase
  17. java如何截取视频文件_Java获取视频时长及截取帧截图详解
  18. MAML:Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks论文精读及详解
  19. 2016中国国际石墨烯创新大会展商名录抢先看
  20. 关于一粒云盘使用心得

热门文章

  1. jquery 总结
  2. 这里有一篇简单易懂的webSocket 快到碗里来~
  3. 网络基础知识--子网划分
  4. 权限问题导致zabbix无法监控mysql
  5. Linux企业级项目实践之网络爬虫(28)——爬虫socket处理
  6. Python学习笔记:访问数据库
  7. C语言函数手册:c语言库函数大全|C语言标准函数库|c语言常用函数查询
  8. 数据分片排序oracle,Oracle数据库的优化
  9. [云炬创业基础笔记]第十章企业的利润计划测试3
  10. pyinstaller打包exe程序教程推荐及需要注意的点