1、ThreadLocal是什么?

ThreadLocal是一个线程内部数据存储类,通过他可以在指定的线程中存储数据。存储后,只能在指定的线程中获取到存储的数据,对其他线程来说无法获取到数据。

2、ThreadLocal的使用场景

日常使用场景不多,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑使用ThreadLocal。
Android源码的Lopper、ActivityThread以及AMS中都用到了ThreadLocal。

3、ThreadLocal的使用示例

public class ThreadLocalActivity extends AppCompatActivity {
private ThreadLocal<String> name = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_thread_local);name.set("小明");Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());new Thread("thread1") {@Overridepublic void run() {name.set("小红");Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());}}.start();new Thread("thread2") {@Overridepublic void run() {Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());}}.start();
}
}

运行结果:

D/ThreadLocalActivity: Thread:main name:小明
D/ThreadLocalActivity: Thread:thread1 name:小红
D/ThreadLocalActivity: Thread:thread2 name:null

可以看到虽然访问的是同一个ThreadLocal对象,但是获取到的值却是不一样的。

4、ThreadLocal的源码阅读

那么为什么会造成这样的结果呢?这就需要去看看ThreadLocal的源码实现,这里的源码版本为API 28。主要看它的get和set方法。
set方法:

 public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}

set方法中首先获取了当前线程对象,然后通过getMap方法传入当前线程t获取到一个ThreadLocalMap,接下来判断这个map是否为空,不为空就直接将当前ThreadLocal作为key,set方法中传入要保存的值最为value,存放到map中;如果map为空就调用createMap方法创建一个map并同样将当前ThreadLocal和要保存的值作为key和value加入到map中。
接下先看getMap方法:

 ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

getMap方法比较简单,就是返回从传入的当前线程对象的成员变量threadLocals。
接着是createMap方法:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

createMap方法也很简单就是new了一个ThreadLocalMap并赋给当前线程对象t中的threadLocals。
原来这个Map是存放在Thread类中的。于是进入Thread类中查看。
Thread.java第188-190行:

/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

根据这里的注释可以得知,每个线程Thread中都有一个ThreadLocalMap类型的threadLocals成员变量来保存数据,通过ThreadLocal类来进行维护。这样看来我们每次在不同线程调用ThreadLocal的set方法set的数据是存在不同线程的ThreadLocalMap中的,就像注释说的ThreadLocal只是起了个维护ThreadLocalMap的功能。想到是get方法同样也是到不同线程的ThreadLocalMap去取数据。
get方法:

 public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}

果然,get方法中同样是先获取当前线程对象,然后在拿着这个对象t去获取到t中的ThreadLocalMap,只要map不等于null就调用map.getEntry(this)方法来获取数据,因为ThreadLocalMap里使用一个内部类Entry来存储数据的,所以调用getEntry(this)方法,传入的key是当前的ThreadLocal。这样获取到Entry类型数据e,只要e不为null,返回e.value即先前存储的数据。如果获取到的map为null又或者根据key获取Entry为null,就调用setInitialValue方法初始化一个value返回。
setInitialValue和initialValue方法:

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}protected T initialValue() {return null;
}

setInitialValue方法中首先调用initialValue方法初始化了一个空value,之后的操作和set方法相同,将这个空的value加入到当前线程的ThreadLocalMap中去,ThreadLocalMap为空就创建个Map,最后返回这个空值。
至此,ThreadLocal的get、set方法就都看过了,也理解了ThreadLocal可以在多个线程中操作而互不干扰的原因。但是ThreadLocal还有一个要注意的地方就是ThreadLocal使用不当会造成内存泄漏。

5、ThreadLocal内存泄漏的原因

内存泄漏的根本原因是当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,导致本该被回收的对象不能被回收而停留在堆内存中。那么ThreadLocal中是在哪里发生的呢?这就要看到ThreadLocalMap中存储数据的内部类Entry。

   static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

可以看到这个Entry类,这里的key是使用了个弱引用,所以因为使用弱引用这里的key,ThreadLocal会在JVM下次GC回收时候被回收,而造成了个key为null的情况,而外部ThreadLocalMap是没办法通过null key来找到对应value的。如果当前线程一直在运行,那么线程中的ThreadLocalMap也就一直存在,而map中却存在key已经被回收为null对应的Entry和value却一直存在不会被回收,造成内存的泄漏。
不过,这一点设计者也考虑到了,在get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,这样在下次回收时就能将Entry和value回收。
这样看上去好像是因为key使用了弱引用才导致的内存泄漏,为了解决还特意添加了清除null key的功能,那么是不是不用弱引用就可以了呢?

很显然不是这样的。设计者使用弱引用是由原因的。

  • 如果使用强引用,那么如果在运行的线程中ThreadLocal对象已经被回收了但是ThreadLocalMap还持有ThreadLocal的强引用,若是没有手动删除,ThreadLocal不会被回收,同样导致内存泄漏。
  • 如果使用弱引用ThreadLocal的对象被回收了,因为ThreadLocalMap持有的是ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。nullkey的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

所以,由于ThreadLocalMap和线程Thread的生命周期一样长,如果没有手动删除Map的中的key,无论使用强引用还是弱引用实际上都会出现内存泄漏,但是使用弱引用可以多一层保护,null key在下一次ThreadLocalMap调用set、get、remove的时候就会被清除。
因此,ThreadLocal的内存内泄漏的真正原因并不能说是因为ThreadLocalMap的key使用了弱引用,而是因为ThreadLocalMap和线程Thread的生命周期一样长,没有手动删除Map的中的key才会导致内存泄漏。所以解决ThreadLocal的内存泄漏问题就要每次使用完ThreadLocal,都要记得调用它的remove()方法来清除。

转载于:https://blog.51cto.com/14263171/2376223

Android进阶你必须要了解的知识:ThreadLocal相关推荐

  1. Android进阶知识:绘制流程(上)

    1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...

  2. Android进阶知识(二十五):Bitmap简介及其高效加载

    Android进阶知识(二十五):Bitmap简介及其高效加载 一.Bitmap   Bitmap代表一个位图,在Android中指的是一张图片,可以是png.jpg等格式的图片.BitmapDraw ...

  3. android进阶知识总结,Android进阶学习有哪些知识点

    Android进阶学习有哪些知识点 发布时间:2020-07-29 12:50:39 来源:亿速云 阅读:114 作者:Leah 本篇文章给大家分享的是有关Android进阶学习有哪些知识点,小编觉得 ...

  4. Android进阶知识:Retrofit相关

    1.前言 Retrofit是什么?Retrofit是一个遵循RESTful设计的进行HTTP网络请求框架的封装,是现在Android端最火的进行网络请求的库.就像Volley是谷歌官方对HttpURL ...

  5. Android进阶笔记:AIDL内部实现详解 (二)

    接着上一篇分析的aidl的流程解析.知道了aidl主要就是利用Ibinder来实现跨进程通信的.既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯.那么 ...

  6. 我的Android进阶之旅------Android利用温度传感器实现带动画效果的电子温度计

    要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...

  7. Android进阶:自定义视频播放器开发(下)

    上一篇文章我们主要讲了视频播放器开发之前需要准备的一个知识,TextureView,用于对图像流的处理.这篇文章开始构建一个基础的视频播放器. 一.准备工作 在之前的文章已经说过了,播放器也是一个vi ...

  8. Android 进阶第二篇——性能优化

    Android 进阶第二篇--性能优化 一些Android书籍喜欢把性能优化放在最后的章节,简单提一提作为内容全面的点缀.在这里我将工具使用和性能优化的一些个人经验放在进阶系列博客的开始,因为我认为防 ...

  9. Android复习系列④之《Android进阶》

    Android进阶 1 Okhttp OkHttpClient相当于配置中心, 所有的请求都会共享这些配置(例如出错是否重试.共享的连接池) . 1.OkHttpCLient中的配置主要有: Disp ...

最新文章

  1. RTMP协议中的Chunk Stream ID (CID)的作用
  2. 核心路由器聚焦三大关键点
  3. 岗位推荐 | 腾讯音乐娱乐招聘推荐算法工程师、推荐后台工程师
  4. Vue style里面使用@import引入外部css, 作用域是全局的解决方案
  5. Python自动化运维之常用模块—OS
  6. gradle 编译java配置文件_java – 如何在编译时使gradle使用正确的JDK?
  7. AD域首次登陆修改密码设置
  8. linux命令地址,[命令] Linux IP 命令 IP(管理地址)
  9. MT4MT5跟单EA系统跨平台
  10. 计算机毕业设计Java智能交通管控系统(源码+系统+mysql数据库+Lw文档)
  11. 赋能房地产科技生态,“城越”加速器首期计划正式开启
  12. 苹果系统连接服务器打印机,Mac系统怎么连接打印机
  13. 人工智能伦理分论坛的报告
  14. 吉林大学计算机学院学位预警,长春新区发布2021年学位预警!7所学校学位告急!...
  15. 原生js制作动画效果
  16. 教职工员工管理MySQL实训_数据库课程设计---教职工管理系统
  17. ssh_dispatch_run_fatal: Connection to 10.119.126.248 port 29418: incorrect signature fatal: Could no
  18. Java实现Excel下载,excel文件流输出到浏览器
  19. 建站技术之csrf django版
  20. Laravel 上传文件大小改为200M

热门文章

  1. 背完这442句英语,你的口语绝对不成问题了
  2. 如何在Linux下创建与解压zip, tar, tar.gz和tar.bz2文件 .
  3. 2个红外传感器循迹原理_红外线光学气体浓度传感器作用原理
  4. rn如何测试数据请求时间_rn最新版测试
  5. java 创建文件夹的方法_Java创建文件夹的方法
  6. java垃圾回收到老年代次数,Java垃圾回收之回收算法
  7. x10i升级android4.0,智再升级 Xperia X10i升Android 2.3
  8. java写入简介_Java关于IO流的介绍
  9. (8)hibernate四种继承映射
  10. 现在学Java有前途吗?Java岗位饱和了吗?