BHBCSC

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

根据ThreadLocal的定义,很容易想到的实现方法是在ThreadLocal内部定义一个Map,Key保存线程,Value保存值。

但是JDK并不是这样完成的,反而是在每个Thread中保存了ThreadLocalMap的引用,而一个ThreadLocalMap中保存着一组Entry实例,Entry实例内部即是一个ThreadLocal和他所存储的Value值。

static class ThreadLocalMap {
        private Entry[] table;

        private int size = 0;

        private int threshold;
        
        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);
        }
        
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
ThreadLocalMap

初始化的方法和HashMap有异曲同工之妙,具体的方法操作我们通过一般使用ThreadLocal的方法来看一看。

 

首先我们一般会重写一个initValue()

protected T initialValue() {
        return null;
    }
initialValue

不出意料方法体内为空,而JDK注释里指出

This method will be invoked the first
time a thread accesses the variable with the {@link #get}
method, unless the thread previously invoked the {@link #set}
method, in which case the {@code initialValue} method will not
be invoked for the thread

意思是这个方法只会在某个线程第一次通过get方法访问变量时调用。除非先行调用了set,此时这个方法不会被调用。

那我们接下来看看set方法和get方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//从当前线程中获得ThreadLocalMap
        if (map != null)//map不为空时直接设值
            map.set(this, value);
        else//为空时,初始化创建一个map(懒加载)
            createMap(t, value);
    }

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
set
 1 private void set(ThreadLocal<?> key, Object value) {
 2             Entry[] tab = table;
 3             int len = tab.length;
 4             int i = key.threadLocalHashCode & (len-1);//根据hashcode获得在数组的索引
 5 
 6             //遍历桶(开发地址法)
 7             for (Entry e = tab[i];
 8                  e != null;
 9                  e = tab[i = nextIndex(i, len)]) {
10                  //获取ThreadLocal
11                 ThreadLocal<?> k = e.get();
12 
13                 //是所需要的Key,设值返回
14                 if (k == key) {
15                     e.value = value;
16                     return;
17                 }
18 
19                 //过期(被回收),替代过期值返回
20                 if (k == null) {
21                     replaceStaleEntry(key, value, i);
22                     return;
23                 }
24             }
25 
26             //获得索引值开始第一个非null的i,新建Entry存入
27             tab[i] = new Entry(key, value);
28             //如果超过threshold扩容(参照HashMap)
29             int sz = ++size;
30             if (!cleanSomeSlots(i, sz) && sz >= threshold)
31                 rehash();
32         }
map.set

 

 1 public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t);//从当前线程中获得ThreadLocalMap
 4         if (map != null) {//map不为空时
 5             ThreadLocalMap.Entry e = map.getEntry(this);//从Map中根据Key(ThreadLocal)找到对应的Entry
 6             if (e != null) {//找到(Entry不为空)
 7                 @SuppressWarnings("unchecked")
 8                 T result = (T)e.value;//值就是Value
 9                 return result;//返回
10             }
11         }
12         return setInitialValue();//map为空 或者 map不为空但没有当前ThreadLocal所存的值(可能存在其他ThreadLocal)
13     //设置当前ThreadLocal的初始值
14     }
get
 1 private Entry getEntry(ThreadLocal<?> key) {
 2             int i = key.threadLocalHashCode & (table.length - 1);
 3             Entry e = table[i];//根据hashcode获得在数组的索引
 4             if (e != null && e.get() == key)//找到(Entry不为空),直接返回
 5                 return e;
 6             else//没有找到(没初始化或哈希冲突)
 7                 return getEntryAfterMiss(key, i, e);
 8         }
 9         
10     private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
11             Entry[] tab = table;
12             int len = tab.length;
13 
14             //遍历桶(开发地址法)
15             while (e != null) {
16                 ThreadLocal<?> k = e.get();
17                 if (k == key)//找到,直接返回
18                     return e;
19                 if (k == null)//发现存在过期的,清理
20                     expungeStaleEntry(i);
21                 else
22                     i = nextIndex(i, len);
23                 e = tab[i];
24             }
25             //遍历到第一个为null的都没找到,即桶内没有该元素,返回null
26             return null;
27         }
map.getEntry

 

我们可以在以上方法中看到,当访问ThreadLocalMap中的Key(ThreadLocal)为空的时候,

getEntry会调用expungeStaleEntry(int staleSlot),set会调用replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot),

这两个方法都是清理空Key(null)的value值的。

是因为ThreadLocal本身存值是存放在Entry中的,当ThreadLocal被程序员主动释放(null)的时候,ThreadLocal在Map中的软引用也立刻被释放,

但是其对应的Value在Map中仍被引用,无法被释放。

而因为是用的开放地址法处理哈希冲突,删除对应值的时候需要重新计算后续元素的哈希值,并也会发现其他没被清理的值。

 1 private int expungeStaleEntry(int staleSlot) {
 2             Entry[] tab = table;
 3             int len = tab.length;
 4 
 5             // 清除指明的点的value
 6             tab[staleSlot].value = null;
 7             tab[staleSlot] = null;
 8             size--;
 9 
10             // 重新计算哈希
11             Entry e;
12             int i;
13             for (i = nextIndex(staleSlot, len);
14                  (e = tab[i]) != null;//一直到第一个空节点
15                  i = nextIndex(i, len)) {
16                 ThreadLocal<?> k = e.get();
17                 if (k == null) {//key为空而entry不为空,需要清除
18                     e.value = null;
19                     tab[i] = null;
20                     size--;
21                 } else {//key不为空,重新计算哈希值
22                     int h = k.threadLocalHashCode & (len - 1);
23                     if (h != i) {
24                         tab[i] = null;
25 
26                         while (tab[h] != null)//开放地址法
27                             h = nextIndex(h, len);
28                         tab[h] = e;
29                     }
30                 }
31             }
32             return i;
33         }
expungeStaleEntry

 

posted on 2017-09-27 10:17  BHBCSC  阅读(105)  评论(0编辑  收藏  举报