美文网首页
ThreadLocal笔记

ThreadLocal笔记

作者: 8989cc121281 | 来源:发表于2018-06-05 15:30 被阅读23次

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

特点:
一般情况下,创建的变量可以被任何一个线程访问并且修改,但是使用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开发艺术探索》

相关文章

  • Android笔记——ThreadLocal原理浅析

    复习和回顾Android知识,梳理笔记 ThreadLocal简介 ThreadLocal一般在开发中不是很常见,...

  • ThreadLocal笔记

    定义:创建线程局部变量的类 特点:一般情况下,创建的变量可以被任何一个线程访问并且修改,但是使用ThreadLoc...

  • ThreadLocal笔记

    ThreadLocal 背景 在android的消息队列中,每个线程都可以创建一个Looper,那么在androi...

  • 《笔记》— ThreadLocal

    概念ThreadLocal是Java中一个用于线程内部存储数据的工具类。ThreadLocal是用来存储数据的,线...

  • ThreadLocal学习笔记

    ThreadLocal内存泄漏中提到ThreadLocal在ThreadLocalMap中是以一个弱引用身份被En...

  • ThreadLocal学习笔记

    前言 ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变...

  • ThreadLocal学习笔记

    一、经典实用场景 1.Spring中事务的隔离级别public abstract class Transactio...

  • ThreadLocal 学习笔记

    在看Looper的源码时,发现有这么个sThreadLocal属性,研究了下 ThreadLocal官方的定义如下...

  • threadlocal 的笔记

    ThreadLocal 并不是Thread,是thread的局部变量,应该叫ThreadLocalVariable...

  • ThreadLocal

    ThreadLocal 简介ThreadLocal 使用ThreadLocal 原理InheritableThre...

网友评论

      本文标题:ThreadLocal笔记

      本文链接:https://www.haomeiwen.com/subject/iutusftx.html