Handler机制之ThreadLocal

2019-10-25  本文已影响0人  李die喋

ThreadLocal

在之前学习handler的时候不知道还有一个ThreadLocal类,要深入handler之前了解ThreadLocal的工作原理是非常有必要的。

在看了一遍ThreadLocal大概的工作原理之后,我有这几个疑问:

  1. ThreadLocal是如何获取到每个线程中的数组的?
  2. 这个数组的作用到底是什么?
  3. 如何通过ThreadLocal获取到每一个线程对应的Looper?
  4. 把变量存储在本地是为了什么?
  5. ThreadLocal的主要工作原理是怎样的?也就是ThreadLocal是如何把每个线程的数组存储的,它底层的结构?

ThreadLocal工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据。数据存储后,只有这个线程可以访问,其他线程都访问不到。


image

通过上面这张图可以知道ThreadLocal是可以通过线程去设置自己的私有变量的值的。因此可以到线程(Thread)类中的源代码去看看,有没有ThreadLocal。。。

Thread

class Thread implements Runnable {
    ```
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ```
}

Thread类中有关ThreadLocal的类只有ThreadLocal.ThreadLocalMap这个对象,说明ThreadLocalMap是个静态类。

ThreadLocal#ThreadLocalMap

static class ThreadLocalMap {
    
    //存储的数据为Entry.且key为弱引用
    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
    }
    
    //table初始容量 16
    private static final int INITIAL_CAPACITY = 16;
    
    //该表根据需要调整大小,table.length必须始终为2的幂
    private Entry[] table;
    
    //负载因子 用于数组扩容
    private int threshold;
    
     //负载因子,默认情况下为当前数组长度的2/3
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    //第一次放入entry数组 初始化数组长度 定义扩容容量
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    }
}

从ThreadLocalMap类可以看到它的内部结构和Map集合并没有关系,它的内部本身维护了一个Entry[] table数组。通过key可以找到这个数组中存的值。

几个想法:

ThreadLocalMap#set()

从给table数组设置来看下是如何执行的

private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        //通过ThreadLocal<?>来计算hash值 作为table数组的下标
        int i = key.threadLocalHashCode & (len-1);
        
        //遍历table数组 解决三种情况下的问题
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            
            //情况一:判断key值是否相同,相同则将之前的数据覆盖掉
            if (k == key) {
                e.value = value;
                return;
            }
            
            //情况二:如果当前Entry对象对应key值为null,就清空所有key为null的数据
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        
        //以上情况都不满足,直接添加
        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
}

上面所说的这三种情况需要再深入的理解一下。

从上面这段给ThreadLocalMap的table数组设值的时候我发现,其实是将ThreadLocal<?>对象经过hash运算得到table数组的下标(i),通过这个值来和存入数的数据做为映射也就是通过for循环来查找数组中的数据,再通过LocalThread<?>(key)和数组中的ThreadLocal<?>是否创建新的Entry数组单元。

ThreadLocalMap#get

再来看下是如何从Entry中得到ThreadLocal<?>的。

set方法中的e.get()方法

public T get() {
    return getReferent();
}

我点击这个方法后,发现跳转到了Reference这个类中。Reference类是一个抽象类,定义了所有参考对象共有的操作,参考对象是与来及收集器紧密合作实施的,此类不能直接子类化。Entry继承于WeakReference,进去看一看。

WeakReference

进入WeakReference竟然只有两个方法

public class WeakReference<T> extends Reference<T> {
    
    //创建一个弱引用给继承于WeakReference的对象
    public WeakReference(T referent) {
        super(referent);
    }
    
    //创建一个新的弱引用,并将这个对象在给定的队列中注册
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
WeakReference<Activity> reference = new WeakReference<>(activity);
Activity activity = reference.get();

在handler中处理任务的时候先判断这个activity是否不为空,不为空再进行接下来的操作。

ThreadLocal的使用

private ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>();
//主线程
thread.set(true);
Log.d(TAG,"mainThread:"+threadLocal.get());
//子线程1
new Thread("Thread1"){
    public void run(){
        threadLocal.set(false);
        Log.d(TAG,"Thread1:"+threadLocal.get());
    }
}.start();
//子线程2
new Thread("Thread2"){
    public void run(){
        Log.d(TAG,"Thread2:"+threadLocal.get());
    }
}.start();

运行结果:

F/TestActivity:mainThread:true
F/TestActivity:Thread1:false
F/TestActivity:Thread2:null

从结果可以看到不同的线程访问的ThreadLocal是同一个对象,但是他们ThreadLocal获取到的值是不一样的。原因就是Thread线程中有ThreadLocal.ThreadLocalMap这样一个变量,这个变量内部是一个数组,用来存储对应线程内的私有变量。通过当前的ThreadLocal<?>去找到对应的值。

ThreadLocal#get

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

先获取当前线程对象,根据线程对象找到它所对应的的ThreadLocalMap对象,map不为空调用getEntry方法获取Entry存储数据的对象。

ThreadLocal#setInitialValue

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //找不到要找的数据就放到table数组中
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);//创建map
    return value;
}
//回到了ThreadLocal的构造方法中
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap#getEntry

private Entry getEntry(ThreadLocal<?> key) {
    //根据key计算出数据下标索引
    int i = key.threadLocalHashCode & (table.length - 1);
    //得到Entry
    Entry e = table[i];
    //不为空就返回
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

ThreadLocalMap#getEntryAfterMiss

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        //key相同直接返回
        if (k == key)
            return e;
        //key为空,清除key==null的所有数据
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    //没有数据直接返回
    return null;    
}

ThreadLocal#get

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal内存泄漏问题

ThreadLocalMap中采用弱引用作为key,涉及到了java的回收机制。

ThreadLocal不能使用强引用

若key使用强引用,当引用的ThreadLocal被回收了,ThreadLocalMap中还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致内存泄漏

清除key的原因

ThreadLocal的set和get方法中都会去清除key==null的数据,具体有两个原因:

避免使用static的ThreadLocal

使用static修饰的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。原因:在java虚拟机加载类的过程中为静态变量分配内存。static变量的生命周期取决于类的生命周期,也就是类被卸载的时候,静态变量才会被销毁并释放内存空间。这里的目的就是为了保持线程被销毁的时候它内部不应该持有对ThreadLocal的引用。

类的生命周期结束和下面三个条件相关:

总结

参考文章

Android Handler机制之ThreadLocal

上一篇下一篇

猜你喜欢

热点阅读