从源码角度理解ThreadLocal

    ThreadLocal类提供一种线程私有的局部变量机制,使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。实际上,每个线程都拥有该ThreadLocal变量的的一个副本,该副本仅对当前线程可见。

    熟悉JVM的小伙伴都知道,程序计数器、Java虚拟机栈和本地方法栈这三个内存空间是线程私有的,那么是否意味着ThreadLocal变量(对象)就是存储在其中的某个内存空间中呢?显然是否定的,ThreadLocal实例与普通的java对象一样,都是存储在Java堆中的,只不过JDK源码使用了某种编程技巧,使得ThreadLocal对象为某个线程私有。接下来我们从JDK源码角度分析一下这是如何实现的奋斗

    要厘清楚ThreadLocal的原理,我们需要从一下几个类着手:

  • Thread类:
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    Thread类中有一个ThreadLocal.ThreadLocalMap类型的属性,这个属性类似于 HashMap结构(JDK7之前),稍后会详细讲解。

  • ThreadLocal类:
   private final int threadLocalHashCode = nextHashCode();
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  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;
    }
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

   上面贴出ThreadLocal类主要的几个方法:  

 1、 threadLocalHashCode属性在ThreadLocalMap集合进行数据存取的过程中起着重要的作用,类似于HashMap根据key的hashCode值来定位存取数据的数组索引一样。正是因为该属性,ThreadLocal对象才能作为key,存储在ThreadLocalMap集合中,即threadLocalHashCode属性让ThreadLocal对象变成"不可变对象"。

 2、get方法:获取当前线程的threadLocals属性(ThreadLocalMap对象),若存在的话就以this为键去当前线程的threadLocals属性中查找Entry,并返回其value;若threadLocals属性不存在,则为当前线程创建一个,并设value为null。

3、set方法:set方法也同样简单,仍然是获取当前线程的threadLocals属性,并以this为键存储键值对。

  • ThreadLocalMap类:
  static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

......
private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
 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;
        }
private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    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();
        }

     ThreadLocalMap类是ThreadLocal类的静态内部类,底层维护了一个Entry类型的数组,这个集合结构十分类似于HashMap(JDK7之前),以ThreadLocal对象作为键,对应的值就是当前线程针对该ThreadLocal变量持有的数据。

    主要的方法就是getEntryset方法:

1、getEntry方法就是根据ThreadLocal(key)获取对应的Entry的过程。流程是将ThreadLocal的threadLocalHashCode属性与Entry数组长度作位运算,其结果作为数组的索引,接着获取该索引处的数组元素(Entry对象),判断该元素是否存在且与其包含的ThreadLocal是否key相同,若满足则返回该Entry,若不满足则执行getEntryAfterMiss方法。

    getEntryAfterMiss方法中的操作比较简单,就是将索引值依次递增向数组后端推进,直到找到目标Entry元素。这一点与HashMap存在区别:HashMap的底层是数组加向链表,若在数组指定索引处找不到目标元素,则会从该元素所在的单向链表开始,向链表后端查找。

2、set方法就是存储键值对的过程,也是先根据ThreadLocal的threadLocalHashCode属性与数组长度作位运算,获得数组索引,然后从该索引处开始,向数组后端查找,若key相同则覆盖,若不存在Entry则新建...


  • Entry类:
 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    Entry类是ThreadLocalMap类的静态内部类,上述已经提及到了,这里需要注意的是Entry类继承了WeakReference<ThreadLocal<?>>类,WeakReference类表示虚引用,继承该类目的就是避免 内存泄漏


如何避免内存泄漏?
1、TreadLocal对象被Thread线程对象的ThreadLocalMap实例持有,即可看成是被线程持有
2、若使用了线程池,且上述线程出于复用目的而继续存活,则按理说ThreadLocal对象将不会被GC
3、但是Entry类继承了WeakReference类,即ThreadLocalMap在选择key时,不是直接选择ThreadLocal实例的"强引用",而是选择其"弱引用"
4、与弱引用关联的对象,只能存活到下次Minor GC,因此避免了内存泄漏。

总结:

    上述从JDK源码角度讲解了ThreadLocal实现线程私有的原理,总的来说,就是每个线程都维护了个ThreadLocalMap类型的属性threadlocals,这个属性类似于HashMap结构,用来存储键值对,key就是ThreadLocal对象,而value就是当前线程针对该ThreadLocal对象所持有的数据,由于threadlocals是线程私有的,所以ThreadLocal对象对应的数据也为该线程私有。

Ending ...大笑


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值