ThreadLocal的原理,以及在Looper是如何应用的?(字节跳动、小米)
这道题想考察什么?
ThreadLocal 是必须要掌握的,这是因为 Looper 的工作原理,就跟 ThreadLocal 有很大的关系,理解 ThreadLocal 的实现方式有助于我们理解 Looper 的工作原理,这篇文章就从 ThreadLocal 的用法讲起,一步一步带大家理解 ThrealLocal。
考察的知识点
- ThreadLocal的内部运行原理
- Looper相关知识
考生应该如何回答
ThreadLocal 可以把一个对象保存在指定的线程中,对象保存后,只能在指定线程中获取保存的数据,对于其他线程来说则无法获取到数据。日常开发中 ThreadLocal 使用的地方比较少,但是系统在 Handler 机制中使用了它来保证每一个 Handler 所在的线程中都有一个独立的 Looper 对象,为了更好的理解 Handler 机制。
ThreadLocal 是什么
ThreadLocal 是一个关于创建线程局部变量的类。
其实就是这个变量的作用域是线程,其他线程访问不了。通常我们创建的变量是可以被任何一个线程访问的,而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问。
那么ThreadLocal是如何确保只有当前线程可以访问呢?我们先来分析一下ThreadLocal里面最重要的两个函数,get(),set()两个函数。
首先看下get()方法中的源码
从code1 和code2 大家应该不难发现,在给ThreadLocal去set值或者get值的时候都会先获取当前线程,然后基于线程去调用getMap(thread),getMap返回的就是线程thread的成员变量threadLocals。所以通过get 和set都执行对于的函数,这样就保证了threadLocal的访问,一定是只能访问或许修改当前线程的值,这就保障了这个变量是线程的局部变量。
那么接下来ThreadLocalMap又是什么呢?
ThreadLocalMap是什么
从源码可以看出来ThreadLocalMap是一个数组,数组里面是继承自弱引用的Entry。弱引用的使用也是为了当出现异常情况,比如死循环的时候内存能得到回收。
再回过头来看一下set()的源码
这里面的代码就很清晰,根据当前线程取出ThreadLocalMap,然后进行存储数据的操作,如果Map为空的话就先创建,再赋值。
探秘一下map.set()
代码中int i = key.threadLocalHashCode & (len-1)与HashMap中key的hash值方法一样,主要是避免hash值的冲突。再往下走是遍历Map有三种情况:1. 找存在的key有效,然后赋值;2. 找存在的key但是无效,替换掉过期的Entry;3. 没找到相同key值,新建一个Entry然后赋值。
那么ThreadLocal在Looper中又是如何应用的呢?
ThreadLocal在Looper中的应用
找到Looper的源码
Looper与线程的关系是一对一的关系,不同线程之间Looper对象是隔离的,那么Looper是怎么保障这一点的呢?通过上面的代码大家应该不难发现Looper初始化是必须调用prepare函数进行,在调用prepare函数的时候代码会执行到code 1,在code1会先去判断当前线程对于的ThreadLocal中是否存在looper的value,如果存在,那么就抛出异常,这样的执行就保证了一个线程只会设置一次Looper。这个代码的执行流程,就是确保了一个线程只有一个ThreadLocal,一个ThreadLocal就只有一个looper。
总结
ThreadLocal是一个创建线程局部变量的类,它的实现机制决定了这个变量的作用域是线程,其他线程访问不了,利用这个机制,可以保障一个线程只有唯一的一个ThreadLocal变量。然后,在looper中通过prepare函数的设计,确保了一个ThreadLocal 只会和一个Looper进行绑定。通过这两个方式确保了一个线程只有一个ThreadLocal变量,一个ThreadLocal变量只有一个Looper,从而形成了一一对应的关系。
最后
我整理了一套Android面试题合集,除了以上面试题,还包含【Java 基础、集合、多线程、虚拟机、反射、泛型、并发编程、Android四大组件、异步任务和消息机制、UI绘制、性能调优、SDN、第三方框架、设计模式、Kotlin、计算机网络、系统启动流程、Dart、Flutter、算法和数据结构、NDK、H.264、H.265.音频编解码、FFmpeg、OpenMax、OpenCV、OpenGL ES】