ThreadLocal源码分析

环境:jdk8

类定义


 
 
  1. public class ThreadLocal<T>

构造方法

 
 
  1. public ThreadLocal() {
  2.    }

可以看到ThreadLocal类是一个范型定义的类,实例化时可以指定实际类型。

该方法实例化一个线程本地变量。



initialValue

initialValue方法源码:

 
 
  1.    protected T initialValue() {
  2.        return null;
  3.    }

可以看到该方法是protected修饰的,是一个范型方法,默认返回null。可以被子类重写。

该方法返回当前线程局部变量的当前线程的“初始值”。

该方法第一次被调用是一个线程通过get方法访问这个变量,除非这个线程预先调用set方法,在这种情况下,该方法将不会被当前线程调用。

正常情况下,该方法最多会被每个线程都调用一次。除非该线程调用remove方法后又调用get方法。

如果需要线程本地变量有一个初始值,那么该方法必须被重写,通常在匿名内部类中重写。


set

set方法源码:

 
 
  1.  public void set(T value) {
  2.        Thread t = Thread.currentThread();
  3.        ThreadLocalMap map = getMap(t);
  4.        if (map != null)
  5.            map.set(this, value);
  6.        else
  7.            createMap(t, value);
  8.    }

首先第二行得到当前运行线程的引用。接下来再调用getMap方法返回当前线程关联的ThreadLocalMap对象:
 
 
  1. ThreadLocalMap getMap(Thread t) {  
  2.      return t.threadLocals;  
  3. }

ThreadLocalMap类是一个自定义的hash map仅用来维持一个threadlocal类。该类作为静态内部类定义在ThreadLocal类中。

在getMap方法中返回当前线程的threadLocals变量,我们去看看Thread类中该变量的定义:

 
 
  1. /* ThreadLocal values pertaining to this thread. This map is maintained
  2.     * by the ThreadLocal class. */
  3.    ThreadLocal.ThreadLocalMap threadLocals = null;

根据该变量的注释,我们知道,threadLocals由ThreadLocal类维持,而ThreadLocal的值和当前线程相关。


接下来set方法的第四行判断与当前线程关联的ThreadLocalMap对象是否为空,若不为空,则调用map的set方法以ThreadLocal为key,value为值:

 
 
  1. private void set(ThreadLocal<?> key, Object value) {
  2.            // We don't use a fast path as with get() because it is at
  3.            // least as common to use set() to create new entries as
  4.            // it is to replace existing ones, in which case, a fast
  5.            // path would fail more often than not.
  6.            Entry[] tab = table;
  7.            int len = tab.length;
  8.            int i = key.threadLocalHashCode & (len-1);
  9.            for (Entry e = tab[i];
  10.                 e != null;
  11.                 e = tab[i = nextIndex(i, len)]) {
  12.                ThreadLocal<?> k = e.get();
  13.                if (k == key) {
  14.                    e.value = value;
  15.                    return;
  16.                }
  17.                if (k == null) {
  18.                    replaceStaleEntry(key, value, i);
  19.                    return;
  20.                }
  21.            }
  22.            tab[i] = new Entry(key, value);
  23.            int sz = ++size;
  24.            if (!cleanSomeSlots(i, sz) && sz >= threshold)
  25.                rehash();
  26.        }

该方法的第8~10行初始化,第12~26根据key去遍历Entry实体数组是否存在传递过来的ThreadLocal,存在则返回,否则调用replaceStaleEntry方法替换旧的Entry实体并返回,当计算得到的Entry为空时,第28行实例化一个Entry,建立关联。


否则调用createMap方法使用当前ThreadLocal对象和当前要设置的值value实例化一个ThreadLocalMap对象,建立map和当前ThreadLocal的关联。

 
 
  1. void createMap(Thread t, T firstValue) {
  2.        t.threadLocals = new ThreadLocalMap(this, firstValue); // 这里是上面提到的threadLocals初始化的地方
  3.    }
 
 
  1.        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2.            table = new Entry[INITIAL_CAPACITY];
  3.            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  4.            table[i] = new Entry(firstKey, firstValue);
  5.            size = 1;
  6.            setThreshold(INITIAL_CAPACITY);
  7.        }

INITIAL_CAPACITY常量的值为16.

可以看到会构造一个Entry数组,用来存放一个ThreadLocal和它的值。

注意这里使用到了延迟初始化的技术:

这里仅仅是初始化了16个元素的引用数组,并没有初始化16个 Entry 对象。而是一个线程中有多少个线程局部对象要保存,那么就初始化多少个 Entry 对象来保存它们。

到了这里,我们可以思考一下,为什么要这样实现了。为什么要用 ThreadLocalMap 来保存线程局部对象呢?原因是一个线程拥有的的局部对象可能有很多,这样实现的话,那么不管你一个线程拥有多少个局部变量,都是使用同一个 ThreadLocalMap 来保存的,ThreadLocalMap 中 private Entry[] table 的初始大小是16。超过容量的2/3时,会扩容。


第四行Entry构造方法存在于静态嵌套类Entry中:

 
 
  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2.            /** The value associated with this ThreadLocal. */
  3.            Object value;
  4.            Entry(ThreadLocal<?> k, Object v) {
  5.                super(k);
  6.                value = v;
  7.            }
  8.        }

在该方法中会构造一个弱引用的ThreadLocal对象。


第五行size=1,即table元素数量为1。只有在我们至少有一个ThreadLocal放入table的时候才会创造一个ThreadLocalMap实例。


最后将关联对象ThreadLocalMap赋值给当前线程的thread-local变量。

由此可以看到set方法设置当前线程的thread-local变量的副本并指定一个初始值,每个线程都保持对其线程局部(thread-local)变量副本的隐式引用。


get

get方法源码:

 
 
  1. public T get() {
  2.        Thread t = Thread.currentThread();
  3.        ThreadLocalMap map = getMap(t);
  4.        if (map != null) {
  5.            ThreadLocalMap.Entry e = map.getEntry(this);
  6.            if (e != null) {
  7.                @SuppressWarnings("unchecked")
  8.                T result = (T)e.value;
  9.                return result;
  10.            }
  11.        }
  12.        return setInitialValue();
  13.    }

首先第二行得到当前运行线程的引用。接下来再调用getMap方法返回当前线程关联的ThreadLocal map对象:

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

ThreadLocalMap类是一个自定义的hash map仅用来维持一个threadlocal类。

接下来判断与当前线程关联的map对象是否为空,不为空,则通过map的getEntry方法得到与ThreadLocal关联的Entry,

接下判断Entry不为空则,得到Entry的值并返回,该值是和ThreadLocal对象关联的值。

否则返回调用setInitialValue方法:

 
 
  1. private T setInitialValue() {
  2.        T value = initialValue();
  3.        Thread t = Thread.currentThread();
  4.        ThreadLocalMap map = getMap(t);
  5.        if (map != null)
  6.            map.set(this, value);
  7.        else
  8.            createMap(t, value);
  9.        return value;
  10.    }

可以看到该方法是set()方法的变种,为了确定并返回初始值。


remove

remove方法源码:

 
 
  1.     public void remove() {
  2.         ThreadLocalMap m = getMap(Thread.currentThread());
  3.         if (m != null)
  4.             m.remove(this);
  5.     }

第二行上面已经分析过,直接看ThreadLocalMap对象的remove方法:

 
 
  1. private void remove(ThreadLocal<?> key) {
  2.            Entry[] tab = table;
  3.            int len = tab.length;
  4.            int i = key.threadLocalHashCode & (len-1);
  5.            for (Entry e = tab[i];
  6.                 e != null;
  7.                 e = tab[i = nextIndex(i, len)]) {
  8.                if (e.get() == key) {
  9.                    e.clear();
  10.                    expungeStaleEntry(i);
  11.                    return;
  12.                }
  13.            }
  14.        }

table变量的定义:

 
 
  1.        /**
  2.         * The table, resized as necessary.
  3.         * table.length MUST always be a power of two.
  4.         */
  5.        private Entry[] table;

由以上分析可以知道table存储的是弱引用的ThreadLocal对象。

第五行根据key的哈希码得到的索引去遍历查找table里面的的Entry并得到Entry持有的引用判断其是否和传递进来的ThreadLocal对象相等,若相等则清除它,并调用expungeStaleEntry方法将索引为i的Entry置为null,数量减1,最后重新计算哈希码。


内存回收

在ThreadLocal 层面的内存回收

根据ThreadLocal类的注释,可以知道每个活着的线程都持有一个thread-local变量的副本并且ThreadLocal实例是可访问的。

当线程死亡时,那么所有的保存在的线程局部变量就会被回收,其实这里是指线程Thread对象中的 ThreadLocal.ThreadLocalMap threadLocals 会被回收,这是显然的。

 
 
  1. /**Each thread holds an implicit reference to its copy of a thread-local
  2. variable as long as the thread is alive and the {@code ThreadLocal}
  3. instance is accessible; after a thread goes away, all of its copies of
  4. thread-local instances are subject to garbage collection (unless other
  5. references to these copies exist). */

ThreadLocalMap 层面的内存回收

根据ThreadLocalMap类的注释,ThreadLocalMap是一个定制的hash map,只用来维持线程局部变量并且是被WeakReferences包装过的弱引用。


 
 
  1.    /**
  2.     * ThreadLocalMap is a customized hash map suitable only for
  3.     * maintaining thread local values. No operations are exported
  4.     * outside of the ThreadLocal class. The class is package private to
  5.     * allow declaration of fields in class Thread.  To help deal with
  6.     * very large and long-lived usages, the hash table entries use
  7.     * WeakReferences for keys. However, since reference queues are not
  8.     * used, stale entries are guaranteed to be removed only when
  9.     * the table starts running out of space.
  10.     */

如果线程可以活很长的时间,并且该线程保存的线程局部变量有很多(也就是 Entry 对象很多),那么就涉及到在线程的生命期内如何回收 ThreadLocalMap 的内存了,不然的话,Entry对象越多,那么ThreadLocalMap 就会越来越大,占用的内存就会越来越多,所以对于已经不需要了的线程局部变量,就应该清理掉其对应的Entry对象。

  
  
  1.        /**
  2.         * Set the resize threshold to maintain at worst a 2/3 load factor.
  3.         */
  4.        private void setThreshold(int len) {
  5.            threshold = len * 2 / 3;
  6.        }

使用的方式是,Entry对象的key是WeakReference 的包装,当ThreadLocalMap 的 private Entry[] table,已经被占用达到了三分之二时 threshold = 2/3(也就是线程拥有的局部变量超过了10个) ,就会尝试回收 Entry 对象,我们可以看到 ThreadLocalMap.set方法中有下面的代码:


  
  
  1. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  2.    rehash();

cleanSomeSlots 就是进行回收内存:

  
  
  1. private boolean cleanSomeSlots(int i, int n) {
  2.            boolean removed = false;
  3.            Entry[] tab = table;
  4.            int len = tab.length;
  5.            do {
  6.                i = nextIndex(i, len);
  7.                Entry e = tab[i];
  8.                if (e != null && e.get() == null) {
  9.                    n = len;
  10.                    removed = true;
  11.                    i = expungeStaleEntry(i);
  12.                }
  13.            } while ( (n >>>= 1) != 0);
  14.            return removed;
  15.        }

e.get() == null 调用的是 Entry 的父类 WeakReference<ThreadLocal<?>> 的方法:

  
  
  1.    public T get() {
  2.        return this.referent;
  3.    }

返回 null ,表示 Entry 的 key 已经被回收了,所以可以回收该 Entry 对象了:

expungeStaleEntry(i)方法源码:

  
  
  1.        private int expungeStaleEntry(int staleSlot) {
  2.            Entry[] tab = table;
  3.            int len = tab.length;
  4.            // expunge entry at staleSlot
  5.            tab[staleSlot].value = null;
  6.            tab[staleSlot] = null;
  7.            size--;
  8.            // Rehash until we encounter null
  9.            Entry e;
  10.            int i;
  11.            for (i = nextIndex(staleSlot, len);
  12.                 (e = tab[i]) != null;
  13.                 i = nextIndex(i, len)) {
  14.                ThreadLocal<?> k = e.get();
  15.                if (k == null) {
  16.                    e.value = null;
  17.                    tab[i] = null;
  18.                    size--;
  19.                } else {
  20.                    int h = k.threadLocalHashCode & (len - 1);
  21.                    if (h != i) {
  22.                        tab[i] = null;
  23.                        // Unlike Knuth 6.4 Algorithm R, we must scan until
  24.                        // null because multiple entries could have been stale.
  25.                        while (tab[h] != null)
  26.                            h = nextIndex(h, len);
  27.                        tab[h] = e;
  28.                    }
  29.                }
  30.            }
  31.            return i;
  32.        }

总结

1)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;

2)线程死亡时,线程局部变量会自动回收内存;

3)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量; 

 key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;

4)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收.



如有疏漏或不清楚请指出,谢谢!


内存回收及总结内容来自:

http://www.cnblogs.com/digdeep/p/4510875.html 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值