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了,但如果直接调用它的getsetremove方法,很显然会出现空指针异常。因为它的生命已经结束了,再调用它的方法也没啥意义。

此时,如果系统中还定义了另外一个ThreadLocal变量b,调用了它的getsetremove,三个方法中的任何一个方法,都会自动触发清理机制,将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线程中更新界面元素时,你希望消息按照其在时间上的先后顺序被处理,以确保正确的显示顺序。


一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。