Android面试Android中的ThreadLocal应用

Posted Rose J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试Android中的ThreadLocal应用相关的知识,希望对你有一定的参考价值。

ThreadLocal是啥?

在多线程中,因为变量是可以共享的,所以就存在了线程安全问题,我们可以通过同步的方式来解决,如果我们对于单个线程的变量,为了避免线程安全,担又不想用同步的方式,我们就可以用到ThreadLocal。

ThreadLocal可以提供我们一个局部变量,而且这个变量与一般变量还不同,他是每个线程独有的,与其他线程互不干扰的。

ThreadLocal怎么用?

ThreadLocal local = new ThreadLocal();
int a = 10;//声明变量
​
​
local.set(a+10);//赋值
local.get()//取值
local.remove();//删除Entry


我们可以在线程A中对变量a进行赋值,并且通过set方法绑定到当前线程的Threadlocal,当要取值时可以通过get方法,取值=20。但是线程B通过同样的方式取值会等于null。这就体现了Threadlocal的特点,将变量独立于单个线程。与其他线程互不干扰。

ThreadLocal原理

get方法

1620305369286

我们调用get的方法时,会根据当前线程得到一个 ThreadLocalMap ,这是一个存储entry,而entry又是通过key-value(键值对)来保存的。而key就是当前线程的ThreadLocal,一个线程只有一个ThreadLocal,ThreadLocal是用final修饰的。

调用get方法就是通过ThreadLocalMap ,去获取key=当前线程的ThreadLocal对应的value。

情况①: ThreadLocalMap 为空,在没有创建ThreadLocalMap 的情况下直接调用get方法,

就会直接调用 setInitialValue 方法

img

在这个方法之中,首先会执行如下语句:

T value = initialValue();
img

所以这种情况下会返回null值

然后再执行

createMap(t, value);
img

此时的this就是当前线程的ThreadLocal实例,value等于null。

结论:这种没有创建ThreadLocalMap,直接调用get的结果是,最终会创建一个

ThreadLocalMap,然后往里面放个值,(key-value)key=当前实例的ThreadLocal,value=null

情况②:首先在线程A中local.set(10),再在线程A中调用local.get()获取的值=10(同一线程)

情况③:首先在线程A中local.set(10),再在线程B中调用local.get()获取的值=null(不同线程)

set方法

我们要对一个变量赋值为10,就首先要调用set方法

img

set方法其实和get方法挺像的, 也是先拿到当前线程,然后根据当前线程得到ThreadLocalMap,这里同样之前没有,所以需要重新创建,也就是去执行:

createMap(t, value);
但是这里的value就不是null了,而是传过来的值10

img

又到了这里,创建了一个新的ThreadLocalMap来存放数据,this同样也是ThreadLocal的实例,也就是local,这样一来,key就对应我们的ThreadLocal实例,value就是传过来的10了,另外我们大概知道,这么个键值对是放在ThreadLocalMap中的,然后我们通过当前线程可以得到这个ThreadLocalMap,再根据ThreadLocal这个实例就可以得到value的值,也就是10.

然后再去调用get方法,就可以通过当前线程的ThreadLocal获取到对应的值啦

,而且ThreadLocalMap也在set方法中创建了。

ThreadLocalMap的源码解读

img

ThreadLocalMap是一种数据结构,他其实维护了一张哈希表,用来存放entry的

,而entry又是以key-value(键值对)的方式存储。

table

img

entry

img

哈希表

数组,key-value存储,初始容量为16,负载因子为0.75(扩容),

(hash冲突可以用开放寻址法)

ThreadLocal内存泄漏

为什么ThreadLocal会出现内存泄漏,我们之前也说过了,Entry对象持有的是键就是ThreadLocal实例的弱引用,弱引用有个什么特点呢?那就是在垃圾回收的时候会被回收掉,可以根据上图想一下,图中虚线就代表弱引用,如果这ThreadLocal实例被回收掉,这个弱引用的链接也就断开了,就像这样:
img

那么这样在Entry对象中的key就变成了null,所以这个Entry对象就没有被引用,因为key变成看null,就取不到这个value值了,再加上如果这个当前线程迟迟没有结束,ThreadLocalMap的生命周期就跟线程一样,这样就会存在一个强引用链,所以这个时候,key为null的这个Entry就造成了内存泄漏。

¥¥¥ -->因为它没有用了,但是还没有被释放。

解决方法

每次使用完 ThreadLocal 就把对应的entry给删除掉,通过调用remove方法

img

这个就是根据key删除掉对应的Entry,如此一来,我们就解决了内存泄漏问题,因为可能出现内存泄漏的Entry,在我们使用完之后就立马删除了。

总结:

1、ThreadLocal是用来提供线程局部变量的,在线程内可以随时随地的存取数据,而且线程之间是互不干扰的。

2、ThreadLocal实际上是在每个线程内部维护了一个ThreadLocalMap,这个ThreadLocalMap是每个线程独有的,里面存储的是Entry对象,Entry对象实际上是个ThreadLocal的实例的弱引用,同时还保存了value值,也就是说Entry存储的是键值对的形式的值,key就是ThreadLocal实例本身,value则是要存储的数据。

3、TreadLocal的核心是底层维护的ThreadLocalMap,它的底层是一个自定义的哈希表,增长因子是2/3,增长因子也可以叫做是一个阈值,底层定义threshold,当哈希表容量大于或等于阈值的3/4的时候就开始扩容底层的哈希表数组table。

4、ThreaLocalMap中存储的核心元素是Entry,Entry是一个弱引用,所以在垃圾回收的时候,ThreadLocal如果没有外部的强引用,它会被回收掉,这样就会产生key为null的Entry了,这样也就产生了内存泄漏。

5、在ThreadLocal的get(),set()和remove()的时候都会清除ThreadLocalMap中key为null的Entry,如果我们不手动清除,就会造成内存泄漏,最佳做法是使用ThreadLocal就像使用锁一样,加锁之后要解锁,也就是用完就使用remove进行清理。

以上是关于Android面试Android中的ThreadLocal应用的主要内容,如果未能解决你的问题,请参考以下文章

Android面试Android中的ThreadLocal应用

腾讯面试官:了解Java Binder中的系统服务吗?

腾讯面试官:了解Java Binder中的系统服务吗?

Android 面试题(转)

Android 面试

Android面试Android跨进程通信IPC