ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ThreadLocal从变量副本的角度解决多线程并发安全问题

2021-10-03 19:03:08  阅读:177  来源: 互联网

标签:副本 ThreadLocalMap ThreadLocal set 线程 key Entry 多线程


ThreadLocal从变量副本的角度解决多线程并发安全问题

之前我们讲的高并发场景下的线程安全问题,可以使用Synchronized同步关键字、Lock手动加锁的方式去解决,什么轻量级锁、偏向锁、重量级锁、可重入锁等等,实际上本质都是控制线程,使得多个线程同步的去访问共享资源。之所以多线程存在线程安全问题,就是因为多个线程访问同一个共享资源导致的,多个线程之间属于竞争关系,很容易就会导致数据的不安全。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nh9HtNqq-1633258255949)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003044313949.png)]

我们说了加锁实际上保证了各个线程之间同步、有序的去访问共享资源,难道不加锁就没有办法解决多线程安全问题了吗?我们要抓主要矛盾,之所以存在并发安全问题,是因为共享资源只有一个,多线程会竞争获取共享资源,如果同一类共享资源有多个,或者说有多个副本给每一个线程使用呢?这样不就不用加锁了,每一个线程只需要存在自己的那个数据的副本即可,因此也就不存在资源竞争问题,也就保证了多线程下数据的安全。而ThreadLocal类就是给每个线程绑定了变量的本地副本,从而避免线程安全。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKDJz1YS-1633258255952)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003044351993.png)]

接下来我们从源码来看看ThreadLocal类是如何给每一个线程保存变量的本地副本的。

首先我们看看Thread线程类,线程类里面有一个ThreadLocalMap类型的成员变量threadLocals,用来存放当前线程的本地变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9paSKNhs-1633258255955)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003044849554.png)]

实际上ThreadLocal类有一个ThreadLocalMap内部类,这个内部类你可以认为是一个专门用来维护线程本地变量HashMap集合

ThreadLocalMap这个类的数据结构是一个Entry类型的数组,用来保存一个个的Entry节点,Entry节点封装了以ThreadLocal实例对象为keyObject对象为value的键值对,保存在ThreadLocalMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hXN7z5v5-1633258255957)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003041216097.png)]

ThreadLocal类实际上是对ThreadLocalMap这个内部类的封装本地变量值最终是存放在ThreadLocalMap中的ThreadLocal类提供了set()get()等其他方法,来操作ThreadLocalMap中保存的数据。

ThreadLocal调用set()方法保存本地变量时,首先获取到当前线程,然后调用getMap()方法得到当前线程的ThreadLocalMap,底层实际上是调用ThreadLocalMapset()方法向这个Map集合中保存数据的在这里插入图片描述

当第一次调用set()方法时,会先调用createMap()方法创建出ThreadLocalMap对象,因此是懒加载的
在这里插入图片描述

接着会使用构造方法创建出ThreadLocalMap对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kiX42MxN-1633258255966)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003045634766.png)]

我们之前说过ThreadLocalMap是一个HashMap集合,因此也有初始容量、加载因子、阈值、散列函数、hashcode值。

ThreadLocalMap的初始容量默认为16,阈值为容量的2/3,利用ThreadLocalhashcode值,对容量进行取模运算,计算出Entry数组中对应的索引位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5EVlC2k2-1633258255968)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003050738848.png)]
在这里插入图片描述

如果ThreadLocalMap之前已经创建出来了,就会调用set()方法向ThreadLocalMap中添加元素。根据hashcode值计算出数组中对应的索引位置,然后遍历这个map所有的Entry,如果key存在了就进行替换,没有的话就将键值对保存到ThreadLocalMap中。同时在遍历的过程中发现key为null,就会清除掉这个数据,并将新的数据存放到这个索引位置。这主要是释放了内存空间,防止内存泄漏

如果既没有发生替换,也没有发生可以清除掉的key,那么就会创建一个Entry,保存到计算出的对应的索引位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcCDm9C9-1633258255973)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003174955938.png)]

我们说过ThreadLocalMap初始容量默认为16,阈值默认为容量的2/3。在向map中添加完数据时,最后还会进行一次清理工作,如果清理后发现当前map的大小还是大于等于阈值,就会触发扩容机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CH22bhiQ-1633258255974)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003173537503.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9oRcu5y4-1633258255976)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003175513153.png)]

ThreadLocalMap扩容机制和HashMap差不多,也是扩容为原来的2倍,然后进行扩容后在再散列,并设置新的阈值。
在这里插入图片描述

讲完了set()方法再来讲一讲get()方法。

get()方法首先获取到当前线程,然后调用getMap()方法得到当前线程对应的ThreadLocalMap。如果这个map不为null,就根据key得到对应Entry,并返回对应的value值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TnwBbfVj-1633258255979)(C:\Users\Jian\AppData\Roaming\Typora\typora-user-images\image-20211003180603028.png)]

如果map为null,比如在第一次调用get()方法,这个map可能还没创建出来。此时会调用setInitialValue()方法来设置初始值,

并返回这个value,value的默认初始值为null。
在这里插入图片描述

我们讲完了ThreadLocal类的set()get()方法,实际上都是一直在操作ThreadLocalMap这个map集合。而ThreadLocalMap中存放的都是一个个的Entry,Entry的key为ThreadLocal对象,value为对应的本地变量。实际上这个EntryWeakReference弱引用的子类,这是为了在JVM进行垃圾回收时,能够自动进行回收,防止内存溢出。真正存储数据备份其实就是这个Entry
在这里插入图片描述

ThreadLocal类本质实际上是以线程作为key,通过数据备份的方式,实现了线程间的数据隔离!

既然进行的数据备份,那么肯定就会造成数据冗余,并且随着线程的存活时间增长,存储的备份数据也会越来越多,即使线程结束了生命周期,这些备份数据也很有可能仍然存在。这样就可能造成内存泄漏,进而导致OOM!

ThreadLocal为了解决内存泄漏的问题,也进行了一些相应的处理,比如将存储备份数据的Entry类设置为弱引用类型,这是为了方便在GC时自动回收。而且在set()get()方法中增加了数据检查,及时清除掉那些key为null的没用的备份数据。

标签:副本,ThreadLocalMap,ThreadLocal,set,线程,key,Entry,多线程
来源: https://blog.csdn.net/qq_39794062/article/details/120597244

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有