ThreadLocal能很方便的解决这个问题,这也就是所谓的线程间数据隔离。Local这个单词有『局部的』意思,并且在源码首行注释中已写明『This class provides thread-local variables.(该类提供线程局部变量)』,所以ThreadLocal的最佳理解是线程局部变量辅助器,通过它能很方便的设置或者获取线程私有的数据。而线程私有的数据也被美其名曰线程局部变量。
open class MockThreadLocal<T> {
/**
* 往当前线程上绑定数据
*/
fun set(value: T) {
val t = Thread.currentThread() as MockThread
val map = getMap(t)
if (map != null)
map.set(this, value as Any?)
else
createMap(t, value)
}
/**
* 获取在当前线程上绑定的数据
*/
fun get(): T? {
// 获取当前的线程
val t = Thread.currentThread() as MockThread
// 获取当前线程持有的ThreadLocalMap
val map = getMap(t)
if (map != null) {
// 如果map不为null,就使用自己作为key来获取value(MockThreadLocal的实例)
val e = map.get(this)
if (e != null) {
return e as T?
}
}
// 如果map为null,设置初始化的值,并返回该值
return setInitialValue()
}
/**
* 移除在当前线程上绑定的数据
*/
fun remove() {
val m = getMap(Thread.currentThread() as MockThread)
m?.remove(this)
}
/**
* 设置初始化的值
*/
private fun setInitialValue(): T? {
val value = initialValue()
val t = Thread.currentThread() as MockThread
val map = getMap(t)
if (map != null)
map.set(this, value as Any?)
else
createMap(t, value)
return value
}
/**
* 默认初始化的值,子类可复写该方法,自定义初始化值
*/
open fun initialValue(): T? {
return null
}
/**
* 创建数据保存类,并赋值给线程
*/
private fun createMap(t: MockThread, value: T?) {
t.threadLocals = ThreadLocalMap(this, value as Any?)
}
/**
* 获取线程中的数据保存类
*/
private fun getMap(t: MockThread): ThreadLocalMap? {
return t.threadLocals
}
... 省略ThreadLocalMap相关代码
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
源码地址
手写ThreadLocal
起源
在Android的
handler消息机制
中looper
是怎么绑定线程的?为什么这样做可以达到绑定线程的目的?想要解答并彻底理解这两个问题那就需要搞明白
ThreadLocal
到底是什么?它又是如何工作的?我们本篇的目的就是先搞明白这两个问题,然后回答上边的两个问题。在开始之前我想先说这么一个观点:一般情况下,大家学习一个
源码原理
的时候,都是通过解读甚至精读源码来学会一个原理,我认为这是背诵式的学习方式,就好比源码写的是一、二、三,我们通过读懂了『一、二、三』从而学会了、知道了他的实现原理,但我们容易忽略本质的问题,就是源码的实现要解决的问题是什么?为什么它这么做就能解决问题?如果让我们做是否能想到这样的方案或者其它的方案?所以对于一些原理性的知识,我认为如果我们能从本质问题出发,从演化的角度去思考它解决问题的方式、去模拟(手写代码)它解决问题的过程,甚至去思考扩展其它的解决方案,我想这样得来的知识才是透彻的、想忘都忘不掉的。ThreadLocal到底是什么?
ThreadLocal
的存在肯定是在解决某个问题的,所以这个问题是什么呢?问题是:如何将数据与线程绑定起来,从而该数据只能在绑定的线程里访问,而其它线程无法访问?
ThreadLocal
能很方便的解决这个问题,这也就是所谓的线程间数据隔离。Local
这个单词有『局部的』意思,并且在源码首行注释中已写明『This class provides thread-local variables.(该类提供线程局部变量)』,所以ThreadLocal
的最佳理解是线程局部变量辅助器
,通过它能很方便的设置或者获取线程私有的数据。而线程私有的数据也被美其名曰线程局部变量
。ThreadLocal是如何工作的?
思考分析
NOTE:该思路与源码是一致的,请放心食用,我们重在复现并理解思路的演化过程。
我们已经了解了问题,那换做我们会如何思考解决呢🤔?现在有这么个思路:
Thread
本身就是个线程对象,可以在其内部用一个Map
数据结构来存储要绑定的数据MockThread
类Map
数据结构类型的成员变量MockThreadLocal
类作为辅助器,专门用来操作当前线程里的Map
set
get
remove
三个API方法MockThreadLocal
的实例作为Map
的key
,并且key
需要使用弱引用进行一次包装MockThreadLocal
的实例就可以很方便的set
get
remove
数据MockThreadLocal
的生命周期将和MockThread
一样长,需要做防止内存泄漏的处理MockThreadLocal
类上定义泛型,该泛型用于存储到Map
里的Value
的类型思路已确定,接下来手写ThreadLocal!
手写ThreadLocal
先写下
MockThread
类,这个比较简单。需要注意的是ThreadLocalMap
是定义在MockThreadLocal
类中的。再写下
MockThreadLocal
类,代码本身并没有难度,我们以get
方法为例分析一把(我把详细的注释加到了代码上)。最后就是写下
ThreadLocalMap
类,该类是实际保存、处理数据的类,代码同样没有难度。其中一个重点就是对弱引用的处理,每次都要尝试清除无用数据,来尽量避免内存泄漏。到这里我们的代码就写完了,可以发现
ThreadLocal
的工作原理,不但没有难度,甚至简单的令人感到意外。需要注意的是源码中ThreadLocalMap没有像我一样直接使用的HashMap,但总体原理思路是一致的,这部分大家可以食用源码来了解测试
对我们的『小轮子』进行测试一把,看是否符合我们的预期。我们定义两个
MockThreadLocal
变量mtl1
mtl2
和两个MockThread
线程。测试case如下:
mtl1
直接调用get
方法的结果(预期输出:null)mtl1.set("二娃_")
后,测试mtl1
调用get
方法的结果(预期输出:二娃_)mtl1.remove()
后,测试mtl1
调用get
方法的结果(预期输出:null)mtl2
直接调用get
方法的结果(预期输出:false)mtl2.set(true)
后,测试mtl2
调用get
方法的结果(预期输出:true)Thread.sleep(200)
操作以保证在线程2先执行完的环境下,在线程2中测试mtl1
直接调用get
方法的结果(预期输出:null)测试代码如下:
测试结果如下:
可以看到测试结果都是符合我们预期的,至此本篇的主要工作就结束了,希望大家都能在不用背的前提下掌握了ThreadLocal原理。撒花!撒花!
问题解答
经过前面的一通操作解答文头的两个问题就是手到擒来的事了
在Android的
handler消息机制
中looper
是怎么绑定线程的?这里肯定是使用
threadLocal的set方法绑定的
,系统源码如下为什么这样做可以达到绑定线程的目的?
这就是
ThreadLocal
的原理部分,ThreadLocal本就是设置或者获取线程私有数据的辅助类,通过它可以很方便的把数据存储到当前线程内部持有的Map数据结构中。文末
个人能力有限,如有不正之处欢迎大家批评指出,我会虚心接受并第一时间修改,以不误导大家。
我的其它文章