ThreadLocal笔记

2018-06-05  本文已影响23人  8989cc121281

定义:
创建线程局部变量的类

特点:
一般情况下,创建的变量可以被任何一个线程访问并且修改,但是使用ThreadLocal创建的变量只能被当前线程访问,其他线程没有办法修改。

实现原理:

set方法

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

首先获取当前线程引用,currentcurrentThread()为本地方法,然后根据当前工作线程的引用获取ThreadLocalMap对象,getMap方法比较简单,返回Thread类的成员变量threadLocals,即ThreadLocalMap对象。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

返回到set方法继续看,如果获取到的ThreadLocalMap对象为空的话则通过createMap方法创建ThreadLocalMap对象,通过直接对threadLocals实例化来创建,代码如下:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以发现ThreadLocalMap对象传入的key值为this,即代表当前ThreadLocal对象,因此每一个都会有一个ThreadLocalMap对象,以当前的ThreadLocal对象为key值存储value对象。

get方法:

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

通过getMap获取到当前线程的ThreadLocalMap对象,然后如果map不为空再以当前ThreadLocal对象为key获取value,从而保证了数据隔离。如果getMap返回null值则需要进行初始化操作,setInitialValue源码和set方法类似,只不过value值是默认值,如下:

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
// 默认会返回null值,可以通过重写来自定义返回值。
protected T initialValue() {
    return null;
}

哈希策略

在createMap方法内部会通过构造方法实例化一个ThreadLocalMap对象,下面看一下Map内部的存储逻辑:

//这里新建了一个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);
    }

这里注意构造方法第三行,获取索引的哈希策略,相关的代码以及注释如下:

private final int threadLocalHashCode = nextHashCode();
// 用于计算hashCode,从零开始,在所有的ThreadLocal间共享。
private static AtomicInteger nextHashCode = new AtomicInteger();
// 由于初始长度为16,每次扩容为以前的两倍,即长度都是2的n次方,使用这个常量尽可能的分布均匀。
private static final int HASH_INCREMENT = 0x61c88647;
// 返回下一个散列码。
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

假设我们set的时候已经创建好了ThreadLocalMap对象,进入ThreadLocalMap对象的set方法看看:

 private void set(ThreadLocal key, Object value) {
        Entry[] tab = table;
        int len = tab.length;//初始为16扩容乘2,所以一直为2的n次幂
        /**  根据key的hash值找到数组的存储位置,使用0x61c88647与上2的n次幂-1,二进制的低位全为1,
              结果就是0x61c88647的低位,可以验证一下,分布均匀的。
         */
        int i = key.threadLocalHashCode & (len-1);
        /** 如果进入for循环,说明出现了碰撞冲突,用到开放寻址法,线性探测
            解决方案是通过+1的方式逐渐遍历数组找到空位
         */
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal k = e.get();
            if (k == key) {   // 如果key值相同则直接替换value
                e.value = value;
                return;
            }
            if (k == null) {   // 找到空位
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 没有碰撞冲突就直接添加
        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
  // nextIndex方法如下
  private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

关于内存泄漏

多数观点:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法。
但是ThreadLocal在调用set或者get方法的时候都会去检测key有没有被回收(调用expungeStaleEntry方法),然后将其value值设置为null,这个方法和WeakHashMap对象的expungeStaleEntries()方法一样。因此value在key被gc后可能还会存活一段时间,但最终也会被回收,但是若不再调用get或者set方法时,那么这个value就在线程存活期间无法被释放。 expungeStaleEntry方法代码如下:

private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // expunge entry at staleSlot
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        // Rehash until we encounter null
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal k = e.get();
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {
                    tab[i] = null;

                    // Unlike Knuth 6.4 Algorithm R, we must scan until
                    // null because multiple entries could have been stale.
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        return i;
    }

本文是为了探究Looper原理记录ThreadlLocal笔记,参考了很多文献如下:
https://blog.csdn.net/u013256816/article/details/51776846
https://my.oschina.net/xianggao/blog/392440?fromerr=CLZtT4xC
《Android开发艺术探索》

上一篇 下一篇

猜你喜欢

热点阅读