头条Android岗三面:说说ThreadLocal是如何工作的?工作原理是什么?(2)

起源

在Android的handler消息机制looper是怎么绑定线程的?为什么这样做可以达到绑定线程的目的?

想要解答并彻底理解这两个问题那就需要搞明白ThreadLocal到底是什么?它又是如何工作的?我们本篇的目的就是先搞明白这两个问题,然后回答上边的两个问题。

在开始之前我想先说这么一个观点:一般情况下,大家学习一个源码原理的时候,都是通过解读甚至精读源码来学会一个原理,我认为这是背诵式的学习方式,就好比源码写的是一、二、三,我们通过读懂了『一、二、三』从而学会了、知道了他的实现原理,但我们容易忽略本质的问题,就是源码的实现要解决的问题是什么?为什么它这么做就能解决问题?如果让我们做是否能想到这样的方案或者其它的方案?所以对于一些原理性的知识,我认为如果我们能从本质问题出发,从演化的角度去思考它解决问题的方式、去模拟(手写代码)它解决问题的过程,甚至去思考扩展其它的解决方案,我想这样得来的知识才是透彻的想忘都忘不掉的

ThreadLocal到底是什么?

ThreadLocal的存在肯定是在解决某个问题的,所以这个问题是什么呢?

问题是:如何将数据与线程绑定起来,从而该数据只能在绑定的线程里访问,而其它线程无法访问?

ThreadLocal能很方便的解决这个问题,这也就是所谓的线程间数据隔离Local这个单词有『局部的』意思,并且在源码首行注释中已写明『This class provides thread-local variables.(该类提供线程局部变量)』,所以ThreadLocal的最佳理解是线程局部变量辅助器,通过它能很方便的设置或者获取线程私有的数据。而线程私有的数据也被美其名曰线程局部变量

ThreadLocal是如何工作的?

思考分析

NOTE:该思路与源码是一致的,请放心食用,我们重在复现并理解思路的演化过程。

我们已经了解了问题,那换做我们会如何思考解决呢🤔?现在有这么个思路:

  1. Thread本身就是个线程对象,可以在其内部用一个Map数据结构来存储要绑定的数据
  2. 就是这种简单的方式,让数据与线程关联起来,就可以达到与线程绑定的目的
  3. 为了模拟我们定义一个MockThread
  4. 在内部定义一个Map数据结构类型的成员变量
  5. 我们定义一个MockThreadLocal类作为辅助器,专门用来操作当前线程里的Map
  6. 因为每次操作的都是当前线程,所以就达到了隔离的目的
  7. 我们对外暴露set get remove三个API方法
  8. 我们将使用MockThreadLocal的实例作为Mapkey,并且key需要使用弱引用进行一次包装
  9. 这样一来对外部使用者来说,只要有MockThreadLocal的实例就可以很方便的set get remove 数据
  10. 因为MockThreadLocal的生命周期将和MockThread一样长,需要做防止内存泄漏的处理
  11. 我们将在MockThreadLocal类上定义泛型,该泛型用于存储到Map里的Value的类型

思路已确定,接下来手写ThreadLocal!

手写ThreadLocal

先写下MockThread类,这个比较简单。需要注意的是ThreadLocalMap是定义在MockThreadLocal类中的。

class MockThread(target: Runnable, name: String) : Thread(target, name) {
//用于保存绑定到线程上的数据
var threadLocals: MockThreadLocal.ThreadLocalMap? = null
}

再写下MockThreadLocal类,代码本身并没有难度,我们以get方法为例分析一把(我把详细的注释加到了代码上)。

open class MockThreadLocal {

/**

  • 往当前线程上绑定数据
    */
    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相关代码
}

最后就是写下ThreadLocalMap类,该类是实际保存、处理数据的类,代码同样没有难度。其中一个重点就是对弱引用的处理,每次都要尝试清除无用数据,来尽量避免内存泄漏。

open class MockThreadLocal {

… 省略代码

/**

  • 定义该类,用于实际保存数据、处理数据
    /
    class ThreadLocalMap(firstKey: MockThreadLocal<
    >, firstValue: Any?) {
    private var mMap: MutableMap<WeakReference<MockThreadLocal<*>>, Any?>? = null

init {
//首次初始化时,设置初始化值
mMap = mutableMapOf(WeakReference(firstKey) to firstValue)
}

/**

  • 设置一个存储的数据
    /
    fun set(key: MockThreadLocal<
    >, value: Any?) {
    //优先清除一次无用数据,防止内存泄漏
    expungeStaleEntry()
    if (mMap != null) {
    var keyExist = false
    mMap!!.forEach { (k, _) ->
    //若相应的key已存在,只需替换该value即可
    if (k.get() == key) {
    mMap!![k] = value
    keyExist = true
    }
    }

//若相应的key不存在,则保存新的数据
if (!keyExist) {
mMap!![WeakReference(key)] = value
}
}
}

/**

  • 获取一个存储的数据
    /
    fun get(key: MockThreadLocal<
    >): Any? {
    //优先清除一次无用数据,防止内存泄漏
    expungeStaleEntry()
    mMap?.forEach { (k, v) ->
    if (k.get() == key) {
    return v
    }
    }
    return null
    }

/**

  • 移除一个存储的数据
    /
    fun remove(key: MockThreadLocal<
    >) {
    //优先清除一次无用数据,防止内存泄漏
    expungeStaleEntry()
    mMap?.forEach { (k, _) ->
    if (k.get() == key) {
    mMap?.remove(k)
    }
    }
    }

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的Android开发中高级必知必会核心笔记,共计2968页PDF、58w字,囊括Android开发648个知识点,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。

虽然面试失败了,但我也不会放弃入职字节跳动的决心的!建议大家面试之前都要有充分的准备,顺顺利利的拿到自己心仪的offer。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值