加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 运营中心 > 网站设计 > 教程 > 正文

正确理解Thread Local的原理与适用场景

发布时间:2018-08-22 03:16:19 所属栏目:教程 来源:郭俊
导读:副标题#e# 技术沙龙 | 邀您于8月25日与国美/AWS/转转三位专家共同探讨小程序电商实战 一、ThreadLocal解决什么问题 由于 ThreadLocal 支持范型,如 ThreadLocal StringBuilder ,为表述方便,后文用 变量 代表 ThreadLocal 本身,而用 实例 代表具体类型(如

上述方案中,出现锁的问题,原因在于多线程访问同一个 Map。如果该 Map 由 Thread 维护,从而使得每个 Thread 只访问自己的 Map,那就不存在多线程写的问题,也就不需要锁。该方案如下图所示。

ThreadLocal side Map

该方案虽然没有锁的问题,但是由于每个线程访问某 ThreadLocal 变量后,都会在自己的 Map 内维护该 ThreadLocal 变量与具体实例的映射,如果不删除这些引用(映射),则这些 ThreadLocal 不能被回收,可能会造成内存泄漏。后文会介绍 JDK 如何解决该问题。

四、ThreadLocal 在 JDK 8 中的实现

1. ThreadLocalMap与内存泄漏

该方案中,Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供。该类的实例维护某个 ThreadLocal 与具体实例的映射。与 HashMap 不同的是,ThreadLocalMap 的每个 Entry 都是一个对 键 的弱引用,这一点从super(k)可看出。另外,每个 Entry 都包含了一个对 值 的强引用。

  1. static class Entry extends WeakReference<ThreadLocal<?>> { 
  2.   /** The value associated with this ThreadLocal. */ 
  3.   Object value; 
  4.   Entry(ThreadLocal<?> k, Object v) { 
  5.     super(k); 
  6.     vvalue = v; 
  7.   } 

使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免上文所述 ThreadLocal 不能被回收而造成的内存泄漏的问题。

但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。

注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对 键 的弱引用),而非具体实例的的弱引用,所以无法避免具体实例相关的内存泄漏。

2. 读取实例

读取实例方法如下所示

  1. public T get() { 
  2.   Thread t = Thread.currentThread(); 
  3.   ThreadLocalMap map = getMap(t); 
  4.   if (map != null) { 
  5.     ThreadLocalMap.Entry e = map.getEntry(this); 
  6.     if (e != null) { 
  7.       @SuppressWarnings("unchecked") 
  8.       T result = (T)e.value; 
  9.       return result; 
  10.     } 
  11.   } 
  12.   return setInitialValue(); 

读取实例时,线程首先通过getMap(t)方法获取自身的 ThreadLocalMap。从如下该方法的定义可见,该 ThreadLocalMap 的实例是 Thread 类的一个字段,即由 Thread 维护 ThreadLocal 对象与具体实例的映射,这一点与上文分析一致。

  1. ThreadLocalMap getMap(Thread t) { 
  2.   return t.threadLocals; 

获取到 ThreadLocalMap 后,通过map.getEntry(this)方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。该方法中的 this 即当前访问的 ThreadLocal 对象。

如果获取到的 Entry 不为 null,从 Entry 中取出值即为所需访问的本线程对应的实例。如果获取到的 Entry 为 null,则通过setInitialValue()方法设置该 ThreadLocal 变量在该线程中对应的具体实例的初始值。

3. 设置初始值

设置初始值方法如下

  1. private T setInitialValue() { 
  2.   T value = initialValue(); 
  3.   Thread t = Thread.currentThread(); 
  4.   ThreadLocalMap map = getMap(t); 
  5.   if (map != null) 
  6.     map.set(this, value); 
  7.   else 
  8.     createMap(t, value); 
  9.   return value; 

该方法为 private 方法,无法被重载。

首先,通过initialValue()方法获取初始值。该方法为 public 方法,且默认返回 null。所以典型用法中常常重载该方法。上例中即在内部匿名类中将其重载。

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读