zoukankan      html  css  js  c++  java
  • ThreadLocal源码分析:(二)get()方法

    在ThreadLocal的get(),set()的时候都会清除线程ThreadLocalMap里所有key为null的value。 
    而ThreadLocal的remove()方法会先将Entry中对key的弱引用断开,设置为null,然后再清除对应的key为null的value。 
    本文分析get方法

    系列文章链接:

    http://www.cnblogs.com/noodleprince/p/8657399.html

    http://www.cnblogs.com/noodleprince/p/8658333.html

    http://www.cnblogs.com/noodleprince/p/8659028.html

    ThreadLocal类的get方法

     1 public T get() {
     2     Thread t = Thread.currentThread();
     3     ThreadLocalMap map = getMap(t);     // 获取线程t中的ThreadLocalMap
     4     if (map != null) {
     5         ThreadLocalMap.Entry e = map.getEntry(this);    // 获取entry,见代码1
     6         if (e != null) {
     7             @SuppressWarnings("unchecked")
     8             T result = (T)e.value;
     9             return result;
    10         }
    11     }
    12     return setInitialValue();   // 没有找到对应的值,调用setInitialValue方法并返回初始值,见代码4
    13 }

    关键逻辑就是去当前线程的ThreadLocalMap中获取对应此ThreadLocal对象的entry,如果获取到了就返回entry的value。否则返回调用setInitialValue方法的结果。

    代码1 
    ThreadLocal.ThreadLocalMap类的getEntry方法

    1 private Entry getEntry(ThreadLocal<?> key) {
    2     int i = key.threadLocalHashCode & (table.length - 1);
    3     Entry e = table[i];
    4     if (e != null && e.get() == key)    // 在key计算hash的位置上直接命中查询,直接返回该entry
    5         return e;
    6     else
    7         return getEntryAfterMiss(key, i, e);        // 没有直接命中,调用getEntryAfterMiss,见代码2
    8 }

    如果在key计算hash的位置上直接命中查询,直接返回该entry,否则调用getEntryAfterMiss并返回结果。

    代码2 
    ThreadLocal.ThreadLocalMap类的getEntryAfterMiss方法

     1 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
     2     Entry[] tab = table;
     3     int len = tab.length;
     4 
     5     while (e != null) {     // 从i位置开始遍历,寻找key能对应上的entry
     6         ThreadLocal<?> k = e.get();
     7         if (k == key)
     8             return e;
     9         if (k == null)
    10             expungeStaleEntry(i);       // 遇到key为null的entry,调用expungeStaleEntry方法,见代码3
    11         else
    12             i = nextIndex(i, len);
    13         e = tab[i];
    14     }
    15     return null;        // 实在没有找到,只能返回null了
    16 }

    在从第i个entry向后遍历的过程中,找到对应的key的entry就直接返回,如果遇到key为null的entry,则调用expungeStaleEntry方法进行清理。

    代码3 
    ThreadLocal.ThreadLocalMap类的expungeStaleEntry方法

     1 private int expungeStaleEntry(int staleSlot) {
     2     Entry[] tab = table;
     3     int len = tab.length;
     4 
     5     // expunge entry at staleSlot
     6     tab[staleSlot].value = null;
     7     tab[staleSlot] = null;
     8     size--;     // 以上代码,将entry的value赋值为null,这样方便GC时将真正value占用的内存给释放出来;将entry赋值为null,size减1,这样这个slot就又可以重新存放新的entry了
     9 
    10     // Rehash until we encounter null
    11     Entry e;
    12     int i;
    13     for (i = nextIndex(staleSlot, len); // 从staleSlot后一个index开始向后遍历,直到遇到为null的entry
    14          (e = tab[i]) != null;
    15          i = nextIndex(i, len)) {
    16         ThreadLocal<?> k = e.get();
    17         if (k == null) {    // 如果entry的key为null,则清除掉该entry
    18             e.value = null;
    19             tab[i] = null;
    20             size--;
    21         } else {
    22             int h = k.threadLocalHashCode & (len - 1);
    23             if (h != i) {   // key的hash值不等于目前的index,说明该entry是因为有哈希冲突导致向后移动到当前index位置的
    24                 tab[i] = null;
    25 
    26                 // Unlike Knuth 6.4 Algorithm R, we must scan until
    27                 // null because multiple entries could have been stale.
    28                 while (tab[h] != null)      // 对该entry,重新进行hash并解决冲突
    29                     h = nextIndex(h, len);
    30                 tab[h] = e;
    31             }
    32         }
    33     }
    34     return i;   // 返回经过整理后的,位于staleSlot位置后的第一个为null的entry的index值
    35 }

    expungeStaleEntry方法不止清理了staleSlot位置上的entry,还把staleSlot之后的key为null的entry都清理了,并且顺带将一些有哈希冲突的entry给填充回可用的index中。

    代码4 
    ThreadLocal类的setInitialValue方法

     1 private T setInitialValue() {
     2     T value = initialValue();   // initialValue()方法直接返回null
     3     Thread t = Thread.currentThread();
     4     ThreadLocalMap map = getMap(t);
     5     if (map != null)
     6         map.set(this, value);   // 调用ThreadLocalMap的set方法
     7     else
     8         createMap(t, value);    // 创建新的ThreadLocalMap,并将value添加进去
     9     return value;
    10 }

    setInitialValue方法里面,真正有难度的就是在map不为null时要去调用set方法了。这种情况会在key(也就是ThreadLocal对象)对应的entry已经被清理过后出现,也有可能是一个没有设置过值的ThreadLocal对象来调用get方法,就会进入到这层逻辑。关于ThreadLocalMap的set方法,在另一篇笔记http://www.cnblogs.com/noodleprince/p/8657399.html中有分析过了,这里就不再贴了。

    ThreadLocalget方法,也可能会触发ThreadLocalMap的清理方法,将ThreadLocalMap中key为null的entry给清理掉,方便GC来回收内存。

  • 相关阅读:
    PHP7 学习笔记(一)Ubuntu 16.04 编译安装Nginx-1.10.3、 PHP7.0.9、Redis3.0 扩展、Phalcon3.1 扩展、Swoole1.9.8 扩展、ssh2扩展(全程编译安装)
    【测试笔记】Redis学习笔记(十二)性能测试
    【官方文档】Nginx负载均衡学习笔记(三) TCP和UDP负载平衡官方参考文档
    高频交易的外部网络连接技术
    国内有哪些顶级高频交易 (HFT) 团队?
    Linux低延迟服务器系统调优
    什么是高频交易系统?
    HFT is a good industry as a computer scientist
    MongoShake——基于MongoDB的跨数据中心的数据复制平台
    QuantStart Content Survey 2020
  • 原文地址:https://www.cnblogs.com/noodleprince/p/8658333.html
Copyright © 2011-2022 走看看