版权声明:本文出自汪磊的博客,转载请务必注明出处。

ThreadLocal简介

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。比如:我们在子线程A中存储数据x,只有在子线程中取数据才会获取存储的数据x,在B线程中获取是获取不到的。存储与获取数据都是要在同一线程中操作。

关于ThreadLocal的使用这里就不举例了(PS:其实是TMD简单了),我们直接分析存储,获取数据核心源码

安卓中ThreadLocal存储数据核心源码分析

ThreadLocal中存储数据调用的是set(T value)方法(T是什么?泛型),源码如下:

 public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

第2行:获取当前线程currentThread,如果在主线程调用set方法,则为主线程,如果在子线程调用则为对应的子线程。

第3行:调用values方法获取Values对象values(PS:这句话挺拗口),看下values方法:

  Values values(Thread current) {
return current.localValues;
}

很简单,就是返回第2行代码获取的currentThread中的localValues,Thread类中定义了一个ThreadLocal.Values类型的变量localValues。

回到set(T value)方法继续看4-6行:如果values为null则对其进行初始化,第一次调用set方法的时候这里肯定为空,我们看下初始化initializeValues方法:

 Values initializeValues(Thread current) {
return current.localValues = new Values();
}

同样很简单,就是对我们获取的当前线程中localValues变量进行初始化。

第7行:调用values的put方法进行数据的存储。

Values是什么鬼呢?怎么最终调用的是Values的put方法?其实Values是ThreadLocal的内部类,内部维护一个Object类型的数组table,我们存储的数据都是存储在内部数组中的,同样获取数据也是调用的Values类中相应方法。 接下来我们分析一下Values类。

ThreadLocal中内部类Values源码分析

先从构造方法看看吧:

 //数组初始化大小
private static final int INITIAL_SIZE = 16;
//存储数据的数组
private Object[] table;
//用于计算存储数据的下标index,长度为table.length - 1,可以防止角标越界
private int mask;
//记录数组中数据数量
private int size; Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
} private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}

这里就没什么要说的了,就是一些初始化方法。接下来我们看下存储数据的方法put(ThreadLocal<?> key, Object value)源码:

 void put(ThreadLocal<?> key, Object value) {
cleanUp(); // Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index]; if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
} if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
} // Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
} // Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}

第6行:firstTombstone 用于记录已经删除的table数组中数据角标,严格说是key的角标,如果有被删除的数据则存储数据的时候在当前位置直接插入即可,节省内存啊。别急,分析完你会理解的。

8-38行遍历table数组中已经存储的数据与将要存储的数据比较,不同情况进行不同的处理(PS:这尼玛不是废话吗)

第8行:int index = key.hash & mask 算出存储数据key的索引,key就是当前的ThreadLocal,还记得这个mask吗?初始化时设置长度为table.length - 1,进行按位与操作计算出的index还可以保障角标不会越界。然后每次循环后index = next(index),next源码如下:

 private int next(int index) {
return (index + 2) & mask;
}

看这里就会明白,index索引每次循环都会加2,说明不是挨个循环table数组中元素,而是0,2,4,6.。。。这种跳跃式循环,为什么这样呢?别急,分析完整个方法你会明白(PS:总告诉我别急,我都快急死了)

第9行:从table数组中取出对应索引的元素

第11-15行:取出的元素key与要存储的key的reference比较,如果相等则在下一个位置index+1,存储value值,然后return整个方法执行完毕。reference又是什么鬼?其实就是当前ThreadLocal的弱引用:

  private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);

也是为了内存考虑的。

11-15行主要判断要存储的数据之前是否存储过,如果存储过则覆盖存储。

17-32行:17行表明之前没有存储过要存储的数据,则进入判断。

18-24行:首先判断firstTombstone是否为-1,firstTombstone默认值-1,上面说了firstTombstone 用于记录已经删除的table数组中数据角标,如果等于-1,则表明没找到已经删除数据的索引。

20-23行:将要存储的数据依次存储在index和index+1的位置上。然后return整个方法。

27-31行:经过上面的分析这里就很好理解了,就是找到有删除的元素,并且firstTombstone就是被删除元素的key的索引,则将要存储数据的key.reference以及value依次存储在firstTombstone与firstTombstone+1位置上,也就是覆盖存储,覆盖已经被删除数据的位置。

35-37行:就是寻找被删除数据角标的过程,其中TOMBSTONE用于记录被删除的数据。

好了,到此整个存储过程就分析完了,是不是还有点蒙蔽,到底怎么存储的呢?上图(是时候展示我强大的画图能力了):

看到了吧,其实存储就是在数组相邻位置依次存储key.reference与value。看到这里上面的所有疑问就都解开了,为什么循环遍历的时候index是每次加2的?因为key的存储是隔一个元素存储的,key.reference与value才是一个数据整体。好了具体存储想说的就都说完了。

我们回头想一个问题?ThreadLocal是怎么将数据只存储在当前线程并且只有当前线程可以获取?其实只要仔细想想文章一开始就提到的ThreadLocal中set方法就不难理解了。

ThreadLocal中set方法一开始就获取当前线程,然后从当前线程获取Values对象,如果当前线程没有则给当前线程初始化一个Values对象,最终调用Values对象的put方法存储数据,获取数据也是同样的逻辑。重点就是无论存储还是获取数据都是调用当前线程中的Values对象的存储或者获取数据的方法,重点是当前线程的Values对象,这个对象只存储在当前线程,其余线程也存在Values对象,但是不同线程是不一样的,不同线程维护不同的Values对象,这就是核心所在。

至于ThreadLocal的get方法我就不继续分析了,最核心的部分上面已经分析完了,感兴趣的可以自己试着去分析一下。

为什么存储的key是当前ThreadLocal的弱引用?

试想一下如果我们存储的是ThreadLocal的强引用,我们将ThreadLocal对象置为null,此时ThreadLocal对应的内存会被顺利回收吗?显然不会,虽然ThreadLocal对象被置为null了,但是Values对象的数组中依然存储着ThreadLocal的强引用,对应内存不能顺利被回收,这就必然造成内存泄漏。

如果存储的是ThreadLocal的弱引用,那么Values对象table数组中对应key则可以被顺利收回,key对应的value在下一次调用的时候会被清除。

这里是不是又会出现一个问题?

由于Values和对应Thread生命周期是一样的,如果我们将ThreadLocal对象置为null,那么table中对应的key就可能被GC回收,当时此时value值还没有回收啊,是不是很别扭,这就要求我们写代码的时候要规范,每次使用完ThreadLocal都调用remove()方法,清除数据。

如下:

 ThreadLocal<String> t1 = new ThreadLocal<String>() {

             @Override
protected String initialValue() {
//
return "adc";
}
}; Toast.makeText(MainActivity.this, t1.get(), 0).show(); t1.remove();
t1 = null;

好了,本文到此结束,希望对你有帮助,废话就不多说了,学到的才是自己的。

下一篇分析Message.obtain()所说的消息池到底是什么玩意?怎么实现的?你真正的理解吗?

Android 异步消息处理机制前篇(一):深入理解ThreadLocal的更多相关文章

  1. Android 异步消息处理机制前篇(二):深入理解Message消息池

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 上一篇中共同探讨了ThreadLocal,这篇我们一起看下常提到的Message消息池到底是怎么回事,废话少说吧,进入正题. 对于稍有经验的开发人员 ...

  2. Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...

  3. Android异步消息机制

    Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...

  4. 深入探讨Android异步精髓Handler

    探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 详解Android主流框架不可或缺的基石 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架 ...

  5. 【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其 ...

  6. Android异步处理系列文章四篇之三

    Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...

  7. Android异步处理系列文章四篇之四 AsyncTask的实现原理

    Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...

  8. Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面

    目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...

  9. Android异步处理系列文章四篇之二 使用AsyncTask异步更新UI界面

    Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...

随机推荐

  1. Winform窗体间传递数据

    public string passText { get { return textBox1.Text; } } //Form1中还有个按钮button1在其点击事件中有: private void ...

  2. gitlab实时备份方案(非官方命令)

    gitlab自带的备份功能做不到实时备份,为了尽可能减少意外情况导致的丢失数据,自己搞了一个实时备份的功能. 备份的大头主要是两部分,数据库和代码库.数据库由DBA配置主备. 仓库经过测试,通过lsy ...

  3. gitlab服务器搭建教程

    gitlab服务器搭建教程 ----2016年终总结 三 参考https://bbs.gitlab.cc/topic/35/gitlab-ce-8-7-%E6%BA%90%E7%A0%81%E5%AE ...

  4. win10 uwp 模拟网页输入

    有时候需要获得网页的 js 执行后的源代码,或者模拟网页输入,如点按钮输入文字. 如果需要实现,那么就需要用 WebView ,使用方法很简单. 首先创建一个 WebView ,接下来的所有输入都需要 ...

  5. 【前端】windows64位必备软件清单

    目录 一.前言 二.日常必备 三.前端相关 四.个人习惯 一.前言 重做系统以后,安装各种软件就是挺烦人的一件事. 特地整理成文章,并且将相关软件上传到了百度网盘,省的以后再各种找资源了. 百度网盘下 ...

  6. (原创)(三)机器学习笔记之Scikit Learn的线性回归模型初探

    一.Scikit Learn中使用estimator三部曲 1. 构造estimator 2. 训练模型:fit 3. 利用模型进行预测:predict 二.模型评价 模型训练好后,度量模型拟合效果的 ...

  7. base64减少图片请求

    1. 使用base64减少 a)            2. 页面解析 CSS 生成的 CSSOM 时间增加 Base64 跟 CSS 混在一起,大大增加了浏览器需要解析CSS树的耗时.其实解析CSS ...

  8. LeetCode 228. Summary Ranges (总结区间)

    Given a sorted integer array without duplicates, return the summary of its ranges. Example 1: Input: ...

  9. LeetCode 119. Pascal's Triangle II (杨辉三角之二)

    Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return [1,3, ...

  10. Hibernate映射类型