并发编程设计模式--ThreadLocal线程本地存储模式

ThreadLocal 其本质上就是避免共享

正所谓是没有共享,就没有伤害.下面来看看ThreadLocal 的源码,Thread类中包含了一个threadLocals变量,它是一种ThreadLocal.ThreadLocalMap类型,该类型定义在ThreadLocal类里面,也就是一个内部类。而ThreadLocalMap这个内部类即是实现了一个Map结构,该类又包含了Entry内部类,ThreadLocal对象和变量值则是通过Entry来保存。

如何模拟实现

在了解了ThreadLocal的功能后我们试着想一个问题:ThreadLocal是如何实现的呢,变量与线程之间如何绑定的呢?实际上,如果让我们自己来实现ThreadLocal功能,我们只要通过一个Map结构就能实现该功能了。其中Map的key是当前线程,而Map的value则是变量值。下图展示了ThreadLocal的设计思想。

该模拟类提供了set、get和remove三个方法,这三个方法都是间接操作Map对象。注意Map对象的key值都是当前线程,由Thread.currentThread()来获取,这个key值不必由调用方传入。这样就实现了一个简单的ThreadLocal,是不是很简单?
以下是java的设计方案

 

可以用下面的简化图来说明

我们的设计方案和 Java 的实现仅仅是 Map 的持有方不同而已,我们的设计里面 Map 属于 ThreadLocal,而 Java 的实现里面 ThreadLocalMap 则是属于 Thread。这两种方式哪种更合理呢?很显然 Java 的实现更合理一些。在 Java 的实现方案里面,ThreadLocal 仅仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在 Thread 里面,这样的设计容易理解。而从数据的亲缘性上来讲,ThreadLocalMap 属于 Thread 也更加合理。

 当然还有一个更加深层次的原因,那就是不容易产生内存泄露。在我们的设计方案中,ThreadLocal 持有的 Map 会持有 Thread 对象的引用,这就意味着,只要 ThreadLocal 对象存在,那么 Map 中的 Thread 对象就永远不会被回收。ThreadLocal 的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄露。而 Java 的实现中 Thread 持有 ThreadLocalMap,而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用(WeakReference),所以只要 Thread 对象可以被回收,那么 ThreadLocalMap 就能被回收。Java 的这种实现方案虽然看上去复杂一些,但是更加安全。

来,我们继续分享源码:

ThreadLocal的set方法

首先获取当前线程对象,然后通过getMap方法获取当前线程的ThreadLocalMap,其实就是从Thread对象中获取,最后调用ThreadLocalMap对象的set方法保存key-value。注意如果Thread对象中的ThreadLocalMap对象为空的话则需要调用createMap方法先创建ThreadLocalMap对象并关联到Thread对象中。

ThreadLocal的get方法

首先获取当前线程对象,然后通过getMap方法获取当前线程的ThreadLocalMap对象,如果该对象不为空则调用ThreadLocalMap对象的getEntry方法获取Entry,Entry对象即包含了我们要的value。如果获取不到值则最终还会执行setInitialValue方法,它是根据ThreadLocal对象的initialValue方法来设置初始值,默认是null,如果你想要设置一个初始值则可以重写initialValue方法

 

ThreadLocal的remove方法

remove方法的逻辑很简单,直接获取当前线程对象的ThreadLocalMap对象,然后调用该对象的remove方法删除对应的key-value。

使用时需要注意 ThreadLocal 与内存泄露

在线程池中使用 ThreadLocal 为什么可能导致内存泄露呢?原因就出在线程池中线程的存活时间太长,往往都是和程序同生共死的,这就意味着 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),弱引用类型在没有其它强引用的情况下会被JVM的垃圾回收器回收,所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。但是 Entry 中的 Value 却是被 Entry 强引用的,所以即便 Value 的生命周期结束了,Value 也是无法被回收的,从而导致内存泄露。

 那在线程池中,我们该如何正确使用 ThreadLocal 呢?其实很简单,既然 JVM 不能做到自动释放对 Value 的强引用,那我们手动释放就可以了,纵观Java中使用ThreadLocal的类。最终都会remove()方法。

try{}finally{}方案,最有效的方案,平时我们可以借鉴。

参考:https://www.jianshu.com/p/812555ba4194

猜你喜欢

转载自www.cnblogs.com/amberJava/p/12452694.html
今日推荐