阿里三面:说说线程封闭与ThreadLocal的关系(上)

简介: 阿里三面:说说线程封闭与ThreadLocal的关系(上)

1 线程封闭

多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了。

数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

避免并发异常最简单的方法就是线程封闭。即把对象封装到一个线程里,只有该线程能看到此对象。

1.1 栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

1.2 ThreadLocal实现线程封闭

ThreadLocal是Java里一种特殊的变量。 它是一个线程级变量,每个线程都有一个ThreadLocal,就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。

  • 用法
ThreadLocal<T> var = new ThreadLocal<T>();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。

可以用 ThreadLocal 存储一些参数, 以便在线程中的多个方法中使用,用来代替方法传参。

实例

13.png

hreadLocal内部维护了一个Map,Map的key是每个线程的名称,Map的value就是我们要封闭的对象。

每个线程中的对象都对应着Map中一个值,即ThreadLocal利用Map实现了对象的线程封闭。


对于CS游戏,开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为1500、0、10。

设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?

如果共享,那么线程之间的并发修改会导致数据不准确。能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的,这个对象就是ThreadLocal。


注意不能将其翻译为线程本地化或本地线程,英语恰当的名称应该叫作:CopyValueIntoEveryThread。


示例代码

14.png

JVM维护了一个Map<Thread, T>,每个线程要用这个T时,用当前的线程去Map里面取。


该示例中,无 set 操作,那么初始值又是如何进入每个线程成为独立拷贝的呢?

虽然ThreadLocal在定义时重写了initialValue(),但并非是在BULLET_ NUMBER_ THREADLOCAL对象加载静态变量的时候执行。而是每个线程在ThreadLocal.get()时都会执行到。


其源码如下

15.png

每个线程都有自己的ThreadLocalMap;

如果map ==null,则直接执行setInitialValue();

如果 map 已创建,就表示 Thread 类的threadLocals 属性已初始化完毕;

如果 e==null,依然会执行到setinitialValue()


setinitialValue()的源码如下:

image.png

这是一个保护方法,CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue()

17.png

getMap的源码就是提取线程对象t的ThreadLocalMap属性: t. threadLocals.


在CsGameByThreadLocal第1处,使用了ThreadLocalRandom 生成单独的Random实例;

该类在JDK7中引入,它使得每个线程都可以有自己的随机数生成器。我们要避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed而导致性能下降。


ThreadLocal是每一个线程单独持有的,因为每一个线程都有独立的变量副本,其他线程不能访问,所以不存在线程安全问题,也不会影响程序的执行性能。

ThreadLocal对象通常是由private static修饰的,因为都需要复制到本地线程,所以非static作用不大。

ThreadLocal无法解决共享对象的更新问题,下面的实例将证明这点:

因为CsGameByThreadLocal中使用的是Integer不可变对象,所以可使用相同的编码方式来操作一下可变对象看看

image.png

输出的结果是乱序不可控的,所以使用某个引用来操作共享对象时,依然需要进行线程同步

image.png

ThreadLocal 有个静态内部类ThreadLocalMap,它还有一个静态内部类Entry;

在Thread中的ThreadLocalMap属性的赋值是在ThreadLocal类中的createMap.


ThreadLocal与ThreadLocalMap有三组对应的方法: get()、set()和remove();

在ThreadLocal中对它们只做校验和判断,最终的实现会落在ThreadLocalMap..

Entry继承自WeakReference,只有一个value成员变量,它的key是ThreadLocal对象


再从栈与堆的内存角度看看两者的关系

image.png

一个Thread有且仅有一个ThreadLocalMap对象

一个Entry对象的 key 弱引用指向一个ThreadLocal对象

一个ThreadLocalMap对象存储多个Entry 对象

一个ThreadLocal 对象可被多个线程共享

ThreadLocal对象不持有Value,Value 由线程的Entry 对象持有.


Entry 对象源码如下

image.png

所有的Entry对象都被ThreadLocalMap类实例化对象threadLocals持有;

当线程执行完毕时,线程内的实例属性均会被垃圾回收,弱引用的ThreadLocal,即使线程正在执行,只要ThreadLocal对象引用被置成null,Entry的Key就会自动在下一次Y - GC时被垃圾回收;

而在ThreadLocal使用set()/get()时,又会自动将那些key=null的value 置为null,使value能够被GC,避免内存泄漏,现实很骨感, ThreadLocal如源码注释所述:

23.png

ThreadLocal对象通常作为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束.


目录
相关文章
|
3月前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
|
3月前
|
安全 Java
多线程线程安全问题之避免ThreadLocal的内存泄漏,如何解决
多线程线程安全问题之避免ThreadLocal的内存泄漏,如何解决
|
3月前
|
存储 安全 Java
多线程线程安全问题之ThreadLocal是什么,它通常用于什么场景
多线程线程安全问题之ThreadLocal是什么,它通常用于什么场景
|
3月前
|
存储 缓存 Java
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
37 0
|
3月前
|
并行计算 算法 安全
Java面试题:解释Java内存模型的内存屏障,并讨论其对多线程并发的影响,解释Java中的线程局部变量(ThreadLocal)的工作原理,解释Java中的ForkJoinPool的工作原理
Java面试题:解释Java内存模型的内存屏障,并讨论其对多线程并发的影响,解释Java中的线程局部变量(ThreadLocal)的工作原理,解释Java中的ForkJoinPool的工作原理
30 0
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1
|
8天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
15天前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
79 10
spring多线程实现+合理设置最大线程数和核心线程数
|
24天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
43 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
NoSQL 网络协议 Unix
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
18 1