ThreadLocal是在我看Handler的Looper.prepare()中看到的
Looper的作用是在MessageQueue中找到相应的Message把它交给Handler
Looper通过Looper.prepare()进行初始化,当然了在主线程不用初始化,它会默认初始化好的
但是在其他线程中必须要写Looper.prepare()方法
进行Looper的初始化操作
一个线程中可以有多个Handler但是只能有一个Looper
为什么呢?这就是因为在Looper进行prepare()的初始化操作中,它用到了ThreadLocal
ThreadLocal
ThreadLocal是一个线程内部的数据储存类,通过他可以在指定的线程中存储数据。数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法获取。
ThreadLocal内部有一个ThreadLocalMap
ThreadLocalMap
- ThreadLocalMap(其内部是一个Entry数组),我们这个ThreadLocalMap是就是Thread的threadLocals变量的。
- ThreadLocalMap内部类Entry继承自WeakReference<ThreadLocal<?>>,内部还包含一个value(键值对形式)
- ThreadLocalMap中Entry数组的默认容量为16, 每次扩容2倍
ThreadLocal.set()
ThreadLocal.set()方法,它是把一个value值保存在了ThreadLocal的一个静态类ThreadLocalMap中,ThreadLocalMap内部有一个Entry,它将我们的ThreadLocal作为键,将我们要保存的那个value作为值保存在了Entry里面
ThreadLocal.get()
这个就是从ThreadLocalMap的Entry中通过键来获得对应的值
ThreadLocal.remove()
将ThreadLocalMap的Entry中ThreadLocal对应的值从Entry中移除
为什么要这么做呢?
因为不这么做的话可能造成内存泄漏问题
ThreadLocal中的内存泄漏
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
这段代码中,我们可以知道因为ThreadLocal是一个WeakReference也就是一个弱引用对象,所以放在Entry中,那么它的键就是一个弱引用对象,然后值是一个强引用对象
当GC回收器开始进行回收操作的时候,ThreadLocalMap的Entry的key就会被回收掉,然后它的值是value,强引用对象,强引用对象怎么会被GC回收器给回收掉,所以就会出现key 为 null 的 value。ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。对于线程池来说,大部分线程是会一直存在于系统的整个生命周期内,那么这样的话,就会造成value对象出现泄漏的可能。
ThreadLocal的相关问题
1.为什么ThreadLocal可以用来进行线程间数据的隔离
ThreadLocal的数据保存在了ThreadLocalMap的Entry里面,它的键是ThreadLocal本身,然后值是你set进去的,所以不同的ThreadLocal,它的值就是不一样的了
2.为什么Looper.prepare()采用的是ThreadLocal
因为Looper它就是为了让一个线程只有一个Looper,不同的线程的Looper不一样
Looper.prepare()的时候它会先进行一个判断,判断当前Looper中的ThreadLocalMap的ThreadLocal对应的值为不为空,如果不为空的话,那么就会直接用这个存在的Looper,如果不为空的话,那么就
调用
sThreadLocal.set(new Looper(quitAllowed));
3.为什么ThreadLocalMap的Entry中键是弱引用对象
其实我当时候想不通为什么Entry的键是弱引用对象,它把它设置为弱引用对象有什么意义
反正它又没办法把value进行一个回收,为什么不把value也变成弱引用对象呢?
因为没有必要
ThreadLocal的set/get/remove方法都会先判断key是否为null,如果为null的话,那么就会把value值移除
然后被gc回收器回收掉
我们把键设置为弱引用对象就是为了在gc回收器回收的时候,它可以为null。
当调用remove/set/get就可以把键为null的清理掉
由于当前的ThreadLocal变量已经被指向null
了,但如果直接调用它的get
、set
或remove
方法,很显然会出现空指针异常
。因为它的生命已经结束了,再调用它的方法也没啥意义。
此时,如果系统中还定义了另外一个ThreadLocal变量b,调用了它的get
、set
或remove
,三个方法中的任何一个方法,都会自动触发清理机制,将key为null的value值清空。
如果key和value都是null,那么Entry对象会被GC回收。如果所有的Entry对象都被回收了,ThreadLocalMap也会被回收了。
这样就能最大程度的解决内存泄露
问题。
那么为什么不把Entry的值也设置成弱引用对象
如果把Entry的值设置成弱引用对象的话,那么就可能出现这种情况:
键在一个GC链路下,而值不在一个GC链路下
当GC回收器回收的时候,只会把值给回收掉
这个时候,你就无法通过键获得对应的值了
所以键得是弱引用对象,值得是强引用对象
4.为什么Handler中一个线程只能有一个Looper
Looper的作用就是从MessageQueue中将Message传递给Handler
说实话,如果可以有多个Looper的话,那么场景就成了多线程安全的问题了
在多线程中处理它的安全最好的方法就是加锁
我在想,Handler中只有一个Looper,它其中一个原因就是为了让MessageQueue里面的Message按照when的大小先后进行
保证when小的一定在前面执行,when大的一定在后面执行
如果是多个Looper进行处理,就可能出现when大的比when小的先执行完
很多时候消息的执行顺序是非常重要的,比如在UI线程中更新界面元素时,你希望消息按照其在时间上的先后顺序被处理,以确保正确的显示顺序。
Comments | NOTHING