阿里三面:说说线程封闭与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对象通常作为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束.


目录
相关文章
|
20小时前
|
存储 Java 测试技术
ThreadLocal:线程专属的变量
ThreadLocal:线程专属的变量
42 0
|
20小时前
|
安全 Java
java中线程经常被问到ThreadLocal你懂吗?
java中线程经常被问到ThreadLocal你懂吗?
11 0
|
20小时前
|
Java 关系型数据库 MySQL
【数据库连接,线程,ThreadLocal三者之间的关系】
【数据库连接,线程,ThreadLocal三者之间的关系】
26 0
|
20小时前
|
存储 安全 Java
调用链跨线程传递 ThreadLocal 对象对比
说起本地线程专属变量,大家首先会想到的是 JDK 默认提供的 ThreadLocal,用来存储在整个链路中都需要访问的数据,并且是线程安全的。由于在落地全链路压测的过程中,一个基本并核心的功能需求是流量标记需要在整个链路中进行传递,那么线程上下文环境成为解决这个问题最合适的技术。
46 2
调用链跨线程传递 ThreadLocal 对象对比
|
20小时前
|
存储 安全 Java
多线程------ThreadLocal详解
多线程------ThreadLocal详解
|
20小时前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
19小时前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
14 1
|
19小时前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
11 0
|
19小时前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
19 1
|
20小时前
|
存储 缓存 安全
【Java多线程】线程安全问题与解决方案
【Java多线程】线程安全问题与解决方案
17 1