ThreadLocal 源码分析

引言

ThreadLocal 可以在每个线程中存取数据,并且不同的线程中的数据互不影响。使用在数据以线程为作用域并且不同的线程拥有不用的数据副本,或者是复杂的参数传递时(参数在同一线程中的不同类中传递)。

在分析消息机制源码的时候,涉及到了 ThreadLocal,使用是在 Looper 类中通过 ThreadLocal<Looper> 对象的 set 方法和 get 方法存取当前线程的 Looper 对象

我们发现 ThreadLocal 对象是一个静态的对象,说明每个线程都可以通过该对象来存取当前线程对应的 Looper ,说明 ThreadLooper 中存放的数据确实是以线程为作用域的,源码接着看

源码分析

看源码之前,先大体的说一下工作的原理。每个线程类 Thread 中都保存了一个 ThreadLocalMap ,可以理解为就是一个 Map,Map 的 key 就是这个 Thread 中所有使用到的 ThreadLocal 对象,Map 的 value 是 Thread 中使用该 ThreadLocal 存储的数据的值。在使用时通过 ThreadLocal 即可存取对应的 value。

可以这么理解。但是 ThreadlocalMap 并不是一个 Map,其内部通过一个 Entry 类型的数组 Entry[] 来存储 ThreadLocal 和其存储的值,Entry 中保存了 value 和 ThreadLocal,Entry 是继承了 WeakReference ,也就是说 Entry[] 是以软弱引用来保存 Entry 的,并且数组中的索引是通过 ThreadLocal 的 hashCode 计算的值,这样根据 ThreadLocal 的 hashCode 也就有了与 Entry[] 中每个索引位置的值之间的关系。

ThreadLocalMap 存在的意义不仅仅是保存 Entry[] 数组,也为该数组中数据的存取提供了更多计算方法

// Entry 中只保存了 value,super 方法中会保存 ThreadLocal
static class Entry extends WeakReference<ThreadLocal> {
    /** The value associated with this ThreadLocal. */
    Object value;
        Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

set() 方法用于数据的存储

// ThreadLocal 的 set() 方法
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 从线程中取出 ThreadLocalMap
    if (map != null)
        map.set(this, value); // 如果 ThreadLocalMap 不为 null ,则使用 ThreadLocalMap 存储
    else
        createMap(t, value); // 如果 ThreadLocalMap 为 null,则为该线程初始化 ThreadLocalMap ,并将数据存储
}


// ThreadLocal 的 getMap() 方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}


// ThreadLocalMap 的 set() 方法
private void set(ThreadLocal key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1); // 根据 ThreadLocal 计算在 Entry[] 数组中的索引值

    // 如果该索引位置有值,则判断 ThreadLocal 是否匹配,不匹配则遍历到下一有值位置
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 
        ThreadLocal k = e.get();

        // 如果有值则判断当前位置的 ThreadLocal 和需要插入的 ThreadLocal 是否相同,如果相同则直接修改 value 为新值
        if (k == key) { 
            e.value = value;
            return;
        }

        // 如果对应位置 ThreadLocal 为空,则取代旧的 Entry ,重新赋值新的 Entry
        if (k == null) {
            replaceStaleEntry(key, value, i); 
            return;
        }
    }

    // 如果该索引位置没有值,则直接赋值为新值
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


// ThreadLocal 的 createMap() 方法
void createMap(Thread t, T firstValue) { // Thread 的 ThreadLocalMap 为 null 时,为 Thread 创建新的 ThreadLocal 并将 ThreadLocal 及 Value 存储
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap 的构造方法
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY]; // 初始化 Entry[] 
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根据 ThreadLocal 计算索引值
    table[i] = new Entry(firstKey, firstValue); 为该索引位置赋值
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

通过在 Thread 中获取 ThreadLocalMap ,再根据 TheadLocal 和 Value 将数据存储到了 Entry[] 数组中,具体的存储过程看注释。

有一个关键点,存储的时候,如果 ThreadLocal 对应的位置有值,则会查看向下一个位置,如果该位置还是有值则继续向下一位置,直到没有值的位置插入该 Entry

存:判断 ThreadLocalMap 是否为空
  • 为空:创建 ThreadLocalMap 并为对应位置赋值

  • 不为空:判断当前位置是否有值 Entry

    • 无值:为当前位置赋值为新的 Entry
    • 有值:判断 ThreadLocal 是否对应
      • 对应:修改旧 Value 值
      • 不对应:向下一有值索引位置判断,直到 ThreadLocal 对应(修改原 Value 值) 或遇到该索引对应值无 ThreadLocal 则为该位置赋值新 Entry,或遇到无值索引时为该位置赋值新 Entry

get() 方法源码解析

// ThreadLocal 的 get() 方法
public T get() {
    Thread t = Thread.currentThread();  // 获取当前线程 Thread
    ThreadLocalMap map = getMap(t); // 获取该线程中的 ThreadLocalMap
    if (map != null) { // 如果 ThreadLocalMap 不为空
        ThreadLocalMap.Entry e = map.getEntry(this); 调用 ThreadLocalMap 的 getEntry 方法获取 Entry 对象
        if (e != null) // Entry 不为空则将 Entry 中存储的 Value 返回
            return (T)e.value;
    }
    return setInitialValue(); // 线程的 ThreadLocalMap 为空或者 ThreadLocalMap 中无对应 Entry 情况
}

// ThreadLocalMap 的 getEntry() 方法
private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1); // 计算在 Entry[] 中的索引
    Entry e = table[i];
    if (e != null && e.get() == key) // 如果 Entry 不为 null 且当前位置对应的 ThreadLocal 为该 ThreadLocal 则返回 Entry
        return e;
    else 
        return getEntryAfterMiss(key, i, e); // 该位置不对应则向下一位置遍历查询
}

// ThreadLocalMap 的 getEntryAfterMiss() 方法
// Entry 为 null 或 ThreadLocal 不对应则向下一个位置遍历,直到 Entry 的 ThreadLocal 和当前 ThreadLocal 对应或遍历结束,如果遍历结束还是 null 则返回 null
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
        while (e != null) {
        ThreadLocal k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}


// ThreadLocalMap 的 setInitialValue() 方发法,线程的 ThreadLocalMap 为空或者 ThreadLocalMap 中无对应 Entry 情况
private T setInitialValue() {
    T value = initialValue(); // 该方法默认返回 null,可重新该方法修改默认值
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);  // ThreadLocalMap 不为空则为该位置创建值为默认值的 Entry
    else
        createMap(t, value); // ThreadLocalMap 为空时,则为该线程创建 ThreadLocalMap 并未该位置创建值为默认值的 Entry
    return value; // 将默认值返回
}

get() 方法主要为从 ThreadLocalMap 中取出对应的值,在该位置没有值或当前线程无对应 ThreadLocalMap 情况下,会返回默认的值 null

取,判断 ThreadLocalMap 是否为空
  • 为空

    • 创建新 ThreadLocalMap 并将该位置赋值 Value 为默认值的 新 Entry ,并返回
  • 不为空

    • 根据 ThreadLocal 计算索引,判断该索引位置是否有值
      • 无值:为该索引位置赋值 Value 为默认值的 新 Entry ,并返回
      • 有值:判断 ThreadLocal 是否匹配
        • 匹配:返回对应位置 Value
        • 不匹配:遍历下一位置直到 ThreadLocal 匹配时返回该 Entry,或该位置的 Entry 的 ThreadLocal 为空时删除该位置之后的所有空条目,继续向下一位置遍历,遍历结束如果还是没有对应 ThreadLocal 则返回 null
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容