sqlserver2008未将对象引用设置到对象的实例_面试官:ThreadLocal 的内存泄漏是弱引用导致的,你确定?...

5ef7d085f0d23d15353a08308112c4d0.png

面试官:ThreadLocal 了解吗?

Python 小星:线程局部变量,多线程下能保证各个线程的变量相对独立于其他线程的变量。

面试官:那你说下它是如何保证线程隔离的?

Python 小星:每个线程维护一个 ThreadLocalMap ,ThreadLocalMap 和 HashMap 结构类似,key 是 ThreadLocal,通过 hash 算法取余平均分配到数组上,value 是我们需要的局部变量。有与 key 的唯一性,保证了线程隔离。

面试官:那你说说 ThreadLocalMap 是数组加链表的结构解决冲突的吗?

Python 小星:我的印象里不是链表结构。

面试官:那他是如何解决冲突的?

Python 小星:......

面试官:创建多线程异步执行业务逻辑时,该 ThreadLocal 变量并不能传递到子线程中,如何让父子线程共享变量?

Python 小星:不太清楚

面试官:InheritableThreadLocal 可以下去了解下。今天就先到这,回去等通知吧


什么是 ThreadLocal?(以下简称 TL)

看下JDK源码注释

e99034ff2f5f75b1655701a28ed75cc5.png

翻译过来:

TL 用来提供线程内部局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问)时能保证各个线程的变量相对独立于其他线程的变量。TL 实例通常来说都是 private static 类型,用于关联线程和线程上下文。

简单说,ThreadLocal 为每个使用该变量的线程提供独立的变量副本。所以每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,我们通常把这叫“线程隔离”。

ThreadLocal 的基本使用

1、常用方法

15f328f6a9d1561a6c8ba31cda19df40.png

2、如果不是 TL,会出现什么问题???

8b5d4eff4b8d0e372e4745a5482ec039.png

输出结果(多运行几次):

894d4e6f3529a1b3b1d770a75bdf7bd2.png

我们可以发现,线程 0 输出了线程 2 的数据。

3、使用 TL

380dc4dc2098fc2ad0e785aac8fddc95.png

TL 如何实现线程隔离???

1、JDK 早期设计

185e9b169e561d75d2e7fef78643eca2.png

熟悉 hashmap 的老铁们,比较容易理解:每个 TL 创建一个 Map,然后用线程作为 Map 的 key,要存储的局部变量作为 Map 的 value,这样达到线程隔离的目的。

这么做有一个弊端:

当线程回收时,该线程绑定的变量不能被自动的回收,因为变量存储在 ThreadLocal 里,必须显式的去回收。

2、JDK 1.8 的设计

每个 Thread 维护一个 ThreadLocalMap,这 map 的 key 是 ThreadLocal 实例本身,value 才是真正要存储的变量值。

f7243395c291c1a98cacff861f0c0fe9.png

① 每个 Thread 内部都有一个 ThreadLocalMap

② ThreadLocalMap 里面存储 TL 对象(key) 和 线程的变量副本(value)

③ Thread 内部的 ThreadLocalMap 是由 TL 维护的,由 TL 负责向 ThreadLocalMap 设置和获取变量值

④ 对于不同的线程,每次获取副本值时,别的线程并不能获取当前线程的副本值,形成副本的隔离,互不干扰

好处:

① 每个 ThreadLocalMap 存储的 Entry 数量变少

② 当 Thread 销毁时,ThreadLocalMap 也随之销毁,减少内存的使用

TL 源码

1、set 方法

6ec936d7026f98a28391384c2ef86da7.png

① 首先获取当前线程,并根据当前线程获取一个 map

② 如果获取的 map 不为空,则将参数设置到 map 中(当前的 TL 的引用设置为 key)

③ 如果 map 为空,则给该线程创建 map ,并设置初始值

2、get 方法

d227e6774dde9c2f38cb43c8dd4bb4c8.png
bc7d75584da8353bd17de89f690b7250.png

初始化

① 获取当前线程,然后根据当前线程获取 map

② 如果 map 不为空,则以 TL 的引用为 key 获取 map 中对应的 Entry e,否则到第 ④ 步

③ 如果 e 不为空,获取对应的 value 值,否则到第 ④ 步

④ map 为空 或者 e 为空,通过 initialValue ,也就是 NULL,然后用 TL 的引用和 value 作为 firstKey 和 firstValue 创建新的 map

总结:获取当前线程的 ThreadLocalMap,如果存在则返回值,不存在则创建并返回值。

3、remove

eadb1605910aa0b7b5cb9f5bd146a132.png

4、ThreadLocalMap

ThreadLocalMap 是 TL 的内部类,没有实现 map 接口。

4534c66c0f72654576725c0f39d52e65.png
0abb34944aeec18975e4394d8ca02023.png

成员变量

从图中,我们可以发现 SHI 和 HashMap 类似。

fbc837243b65636ae059ce3c9b6e6087.png

Entry

Entry 继承 WeakReference,也就是说 key 是弱引用。

弱引用和内存泄漏

1、弱引用相关概念

① java 引用

4 种类型:强、弱、软、虚

② 强引用和弱引用

【强引用】:最常见的 new 一个对象,只要强引用指向一个对象,GC 就不会回收

【弱引用】:GC 一旦发现弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

2、内存溢出和内存泄漏

① 内存溢出(memory overflow)

没有足够的内存提供申请者使用。就像一个开水瓶,当你倒水时,如果大于水瓶容量,水就会溢出来。

② 内存泄漏(memory leak)

动态分配的堆内存由于某种原因未释放或者无法释放,造成系统内存的浪费,内存泄漏的堆积将造成内存泄漏。

为什么 TL 里使用弱引用?

d610fbe9b1010013ce590cad5c1a3026.png

首先我们明确 key 是使用了弱引用,当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 就可以顺利被 gc 。

我们从图中也能看到虽然 key 被 gc 回收了,但是 value 还在呀。因为存在一条从 current thread 连接过来的强引用。只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开,Current Thread, Map, value 将全部被 GC 回收。

有人会问:无论是强引用还是弱引用,都会出现内存泄漏?用弱引用的好处到底体现在哪?

弱引用会将 key 设置为 null,当使用 map 的 get 和 set 方法时,会判断 key 是否为 null ,如果为 null,则将 value 设置为 null。弱引用比强引用在 thread 结束之前多一层屏障。

如何解决内存泄漏问题

当线程的某个 localThread 使用完,然后调用 threadlocal 的 remove 方法。

ThreadLocalMap 和 HashMap 的区别?

ThreadLocalMap 和 HashMap 最大的区别在于解决 hash 冲突。

HashMap 使用的拉链法,而 ThreadLocalMap 使用的线性探测法。

42e16039f7ac380b85673389d7348c13.png

简单说:

当我们通过 int i = key.threadLocalHashCode & (len-1) 计算出 hash 值,如果出现冲突,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

正因为采取的线性探测法解决冲突,所以在查找的时候,必须比较 key 值是否相等,否则顺序寻找下一个单元。

fd96d9549e00a29bf380d7affec10357.png

父子线程如何共享变量?

使用 InheritableThreadLocal 来解决,底层原理的话各位看官下去看看,下次再续。

edc7e136ff0b8ddfbf86e96bc873983a.png

输出结果:

3d5dd731ca99c89bfd8db796485efff4.png

@Python大星 | 文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值