线程安全的set_「Java」 - 多线程四 amp; ThreadLocal
一、ThreadLocal简单使用
多线程访问同一个共享变量容易出现并发问题,特别是多个线程对一个共享变量进行写入时,为了保证线程安全,一般需要在访问共享变量时进行的同步,增加了使用者的负担。在JDK 1.2开始引入ThreadLocal,提供了线程本地变量。
如果创建一个ThreadLocal变量,访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作的自己本地内存里面的变量,从而避免了线程安全问题。
![](/assets/blank.gif)
A、不使用static修饰ThreadLocal
public
使用包含ThreadLocal的对象,结果每个线程都能访问自己的线程本地变量。
threadOne A : null
threadTwo A : null
threadOne B : threadOne local variable
threadTwo B : threadTwo local variable
B、使用static修饰ThreadLocal
如果不使用static修饰,会导致建立一个对象,内部都会创建一个ThreadLocal实例,如果使用修饰为static,则所有的实例共享一个ThreadLocal实例。建议使用final static修饰ThreadLocal变量。
二、ThreadLocal原理
![](/assets/blank.gif)
在Thread类中有个成员变量threadLocals,其默认值为null。
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap,默认每个线程中这个变量都为null,只有当前线程第一次调用了ThreadLocal的set或者get方法时候才会创建threadLocals变量。
其实每个线程的本地变量不是存放到ThreadLocal实例里面的,而是存放到调用线程的threadLocals变量里面,也就是说,ThreadLocal类型的本地变量是存放到具体的线程内存空间的。
ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面存放起来,当调用线程调用它的get方法时再从当前线程的threadLocals变量里面拿出来使用。
final
使用反射获取每个线程的ThreadLocalMap,结果每个线程的ThreadLocalMap是不同的。
threadOne A : null
threadTwo A : null
threadOne B : threadOne local variable
threadTwo B : threadTwo local variable
threadOne C : java.lang.ThreadLocal$ThreadLocalMap@214cda35
threadTwo C : java.lang.ThreadLocal$ThreadLocalMap@eb514c1
每个线程内部都有一个名字为threadLocals的成员变量,该变量类型为HashMap,其中key为定义的ThreadLocal变量的this引用,value则为set时候的值,每个线程的本地变量是存到线程自己的内存变量threadLocals里面的,如果当前线程一直不消失那么这些本地变量会一直存在,从而可能会造成内存溢出,因此使用完毕后需要调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。
ThreadLocalMap
三、ThreadLocal导致的内存泄漏
ThreadLocal只是一个工具类,具体存放变量的是在线程的threadLocals变量里,threadLocals是一个ThreadLocalMap类型。ThreadLocalMap内部是一个Entry数组,Entry继承自WeakReference,Entry内部的value用来存放通过ThreadLocal的set方法传递的值。
A、ThreadLocal内存泄漏的原因
Entry中key使用的是对ThreadLocal对象的弱引用,这为避免内存泄露是一个进步,如果是强引用,即使其他地方没有对ThreadLocal 对象的引用,ThreadLocalMap中的ThreadLocal对象还是不会被回收,而如果是弱引用则这时候ThreadLocal引用是会被回收掉的。
线程的 ThreadLocalMap里面的key是弱依赖,则当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会被在gc的时候回收,但是对应value还是会造成内存泄露,这时候ThreadLocalMap里面就会存在key为null但是value不为null的entry项。
在使用线程本地变量完毕后,还是手动调用remove方法解决内存泄露。
B、线程池中ThreadLocal的内存泄漏
static
由于没有调用线程池的shutdown或者shutdownNow方法所以线程池里面的用户线程不会退出,JVM 进程也不会退出。
使用Idea下的VisualVM Launcher插件配置jvisualvm查看进程内存占用。
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
当主线程处于休眠时候进程占用了大概75+M内存,加入remove代码运行结果则占用了大概25M内存,可知不增加remove时发生了内存泄露。
四、InheritableThreadLocal
A、子线程无法获取父线程中设置的ThreadLocal变量的值
// 创建线程变量
同一个ThreadLocal变量在父线程中设置值后,子线程中是获取不到的。
因为子线程调用get方法时候当前线程为子线程,而调用set方法设置线程变量是main线程,两者是不同的线程,子线程访问时候返回null。
main:hello world
thread:null
B、通过InheritableThreadLocal获取父线程本地变量
为了解决子线程获取父线程本地变量问题,JDK提供InheritableThreadLocal解决。
InheritableThreadLocal继承了ThreadLocal,重写了createMap方法,当第一次调用set方法时候创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals。
调用get方法获取当前线程的内部map变量时候,获取的是inheritableThreadLocals而不再是threadLocals。
public
结果可以获取父线程本地变量。
main:hello world
thread:hello world
五、ThreadLocalRandom
java.util.Random是应用广泛的随机数生成工具类。根据JDK介绍,是线程安全的。
Instances of {@code java.util.Random} are threadsafe.
A、nextInt
public
在单线程情况下每次调用nextInt都是根据老的种子计算出来新的种子,可以保证随机数产生的随机性。
但是在多线程下,多个线程可能都拿同一个老的种子去计算新的种子,会导致多个线程产生的新种子是一样的,由于计算随机数算法是固定的,会导致多个线程产生相同的随机值。
多个线程在根据同一个老种子计算新种子的时候要保证原子性,第一个线程的新种子计算出来后,第二个线程要丢弃自己老的种子,要使用第一个线程的新种子来计算自己的新种子,依次类推,才能保证多线程下产生的随机数是随机的。
B、next
Random函数使用一个原子变量达到了这个效果,在创建Random对象时初始化的种子就保存到了种子原子变量里面。
protected
使用CAS操作,用新的种子去更新老的种子,多线程下可能多个线程都同时执行到了代码oldseed,那么可能多个线程拿到的当前种子的值是同一个,然后执行步骤nextseed计算的新种子也都是一样的,但是while循环中的CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环重新获取更新后的种子作为当前种子去计算老的种子,保证了随机数的随机性。return处则使用固定算法根据新的种子计算随机数。
每个Random实例里面有一个原子性的种子变量用来记录当前种子的值,当要生成新的随机数时要根据当前种子计算新的种子并更新回原子变量。多线程下使用单个Random实例生成随机数,多个线程同时计算随机数计算新种子的时候,它们会竞争同一个原子变量的更新操作,因为原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这是会降低并发性能,于是ThreadLocalRandom应运而生。
C、ThreadLocalRandom
为了解决多线程高并发下Random的缺陷,JUC包下新增了ThreadLocalRandom类。
public
调用ThreadLocalRandom.current()来获取当前线程的随机数生成器。
ThreadLocal通过让每一个线程拷贝一份变量,每个线程对变量进行操作时实际是操作自己本地内存里面的拷贝,从而避免了对共享变量进行同步。
ThreadLocalRandom的实现也是这个原理,Random的缺点是多个线程会使用同一个原子性种子变量,会导致对原子变量更新的竞争。
![](/assets/blank.gif)
每个线程维护一个自己的种子变量,每个线程生成随机数时根据自己老的种子计算新的种子,并使用新种子更新老的种子,然后根据新种子计算随机数,就不会存在竞争问题,这会大大提高并发性能。
六、Spring Request Scope作用域
Spring在XML里面配置Bean时可以指定scope属性来配置Bean的作用域为singleton、prototype、request、session等,其中作用域为request的实现原理就是使用ThreadLocal实现。
线程安全的set_「Java」 - 多线程四 amp; ThreadLocal相关推荐
- java 同步解决不安全类_「JAVA」Java 线程不安全分析,同步锁和Lock机制,哪个解决方案更好...
线程不安全 线程不安全的问题分析:在小朋友抢气球的案例中模拟网络延迟来将问题暴露出来:示例代码如下: public class ImplementsDemo { public static void ...
- android string拼接字符串_「JAVA」细述合理创建字符串,分析字符串的底层存储,你不该错过...
Java基础之字符串操作--String 字符串 什么是字符串?如果直接按照字面意思来理解就是多个字符连接起来组合成的字符序列.为了更好的理解以上的理论,我们先来解释下字符序列,字符序列:把多个字符按 ...
- 「Java」基于Mirai的qq机器人开发踩坑笔记(其一)
目录 0. 前置操作 I. 安装MCL II. MCL自动登录配置 III. 安装IDEA插件 1. 新建Mirai项目 2. 编写主类 3. 添加外部依赖 4. IDEA运行 5. 插件打包 6. ...
- 「Java」基于Mirai的qq机器人开发踩坑笔记(其二)
目录 0. 配置机器人 1. onLoad方法 2. onEnable方法 3. 消息属性 4. 消息监听 I. 好友消息 II. 群聊消息 III. 无差别消息 5. 发送消息 I. 文本消息 II ...
- java 线程间通信方式_「转」JAVA多线程之线程间的通信方式
1. 同步 这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信. public class MyObject { synchronized public void m ...
- 「JAVA」通过抢气球案例,来梳理线程基础知识
程序在没有流程控制的前提下,代码都是从上而下逐行依次执行的.基于这样的机制,如果我们使用程序来实现边打游戏,边听音乐的需求时,就会很困难:因为按照执行顺序,只能从上往下依次执行:同一时刻,只能执行听音 ...
- 「面试复习」「Java」三、Java并发
目录 (一)Java高并发基础 1)多线程的优势和使用场景? 2)同步和异步? 3)并发和并行? 4)线程和进程? 5)阻塞(Blocking)和非阻塞(Non-Blocking)? 6)死锁(Dea ...
- 「Java」- 八大排序
目录 前言 1.冒泡排序 2.选择排序 3.插入排序 4.希尔排序 5.堆排序 6.快速排序 7.归并排序 8.计数排序 前言 由于本章介绍的大多数排序都需要用到数组两个元素之间进行交换操作 , 所以 ...
- 下沉、重聚、归位背后,是一位学者转身业界带领阿里iDST建立技术「影响力」的四年
撰文 | 李九喻 王艺 编辑 | 刘燕 两个月前,在杭州阿里巴巴西溪园区的一间会议室里,机器之能见到了阿里巴巴 iDST 院长金榕. 金榕风风火火地进来,在会议桌靠近门口的一侧坐下.他说话很快,由于长 ...
- java 协程线程的区别_为什么 Java 坚持多线程不选择协程?
谢邀. 先说结论:协程是非常值得学习的概念,它是多任务编程的未来.但是Java全力推进这个事情的动力并不大. 先返回到问题的本源.当我们希望引入协程,我们想解决什么问题.我想不外乎下面几点:节省资源, ...
最新文章
- Java字符串的子串
- npoi生成的表格数字左上角_如何用openpyxl自动化编写Excel电子表格 进阶篇 下
- 网络安全技术文章征稿启事
- 【本站原创】台大林轩田《机器学习基石》系列课程教材的习题解答和实现
- 【Linux】一步一步学Linux——sh命令(225)
- mailbox 编程_往死里写——从站mailbox实现 | 学步园
- 内存溢出和内存泄漏的区别,产生原因以及解决方案
- Android 8.0学习(8)---内核文件系统优化
- 找回Win8.1(windows server 2012 R2)的双拼
- 16.What is pass in Python?
- Python之logging模块
- 【渝粤教育】国家开放大学2018年春季 0554-22T立体构成(一) 参考试题
- ADC的指标详细定义,SNR,以下内容无关: -------------------------------------------分割线----------------SNDR,SFDR,THD等
- ASP.NET中的EnableViewState
- 收藏有关信号处理的博客
- gentoo linux u盘安装,Gentoo系统安装步骤详解
- Bootstrap实战(第一弹:栅格实现5等分或8等分)
- 联想服务器开机滴一下不显示,电脑开机没有滴的一声显示器不亮怎么办 电脑开机故障解决方法【图文】...
- 公众号怎么设置滑动文字_微信公众号滑动文字怎么制作内容呢?
- vue实现刷新页面随机切换背景图【适用于登陆界面】