Android 异步消息处理机制前篇(一):深入理解ThreadLocal
版权声明:本文出自汪磊的博客,转载请务必注明出处。
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的更多相关文章
- Android 异步消息处理机制前篇(二):深入理解Message消息池
版权声明:本文出自汪磊的博客,转载请务必注明出处. 上一篇中共同探讨了ThreadLocal,这篇我们一起看下常提到的Message消息池到底是怎么回事,废话少说吧,进入正题. 对于稍有经验的开发人员 ...
- Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系
版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...
- Android异步消息机制
Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...
- 深入探讨Android异步精髓Handler
探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 详解Android主流框架不可或缺的基石 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架 ...
- 【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程
ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其 ...
- Android异步处理系列文章四篇之三
Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...
- Android异步处理系列文章四篇之四 AsyncTask的实现原理
Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...
- Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面
目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...
- Android异步处理系列文章四篇之二 使用AsyncTask异步更新UI界面
Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...
随机推荐
- Winform窗体间传递数据
public string passText { get { return textBox1.Text; } } //Form1中还有个按钮button1在其点击事件中有: private void ...
- gitlab实时备份方案(非官方命令)
gitlab自带的备份功能做不到实时备份,为了尽可能减少意外情况导致的丢失数据,自己搞了一个实时备份的功能. 备份的大头主要是两部分,数据库和代码库.数据库由DBA配置主备. 仓库经过测试,通过lsy ...
- gitlab服务器搭建教程
gitlab服务器搭建教程 ----2016年终总结 三 参考https://bbs.gitlab.cc/topic/35/gitlab-ce-8-7-%E6%BA%90%E7%A0%81%E5%AE ...
- win10 uwp 模拟网页输入
有时候需要获得网页的 js 执行后的源代码,或者模拟网页输入,如点按钮输入文字. 如果需要实现,那么就需要用 WebView ,使用方法很简单. 首先创建一个 WebView ,接下来的所有输入都需要 ...
- 【前端】windows64位必备软件清单
目录 一.前言 二.日常必备 三.前端相关 四.个人习惯 一.前言 重做系统以后,安装各种软件就是挺烦人的一件事. 特地整理成文章,并且将相关软件上传到了百度网盘,省的以后再各种找资源了. 百度网盘下 ...
- (原创)(三)机器学习笔记之Scikit Learn的线性回归模型初探
一.Scikit Learn中使用estimator三部曲 1. 构造estimator 2. 训练模型:fit 3. 利用模型进行预测:predict 二.模型评价 模型训练好后,度量模型拟合效果的 ...
- base64减少图片请求
1. 使用base64减少 a) 2. 页面解析 CSS 生成的 CSSOM 时间增加 Base64 跟 CSS 混在一起,大大增加了浏览器需要解析CSS树的耗时.其实解析CSS ...
- LeetCode 228. Summary Ranges (总结区间)
Given a sorted integer array without duplicates, return the summary of its ranges. Example 1: Input: ...
- 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, ...
- Hibernate映射类型