带你揭秘神秘的ThreadLocal

说起 ThreadLocal
大家应该有种很熟悉的感觉,但是又好像不知道是干啥用的,第一次接触它还是在Looper的源码中,每次获取Looper对象是,通过ThreadLocal的get方法获取到当前线程的Looper对象,有兴趣的可以看看之前的文章 Android源码学习之handler
,为什么要通过ThreadLocal来获取Looper对象呢,亦或者说这样做有什么好处?今天就带大家一起深入了解这个神秘的ThreadLocal。

源码

话不多说,直接开撸:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 */
public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    public ThreadLocal() {
    }

    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();
    }
    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实例通常是类中的私有静态字段,希望将状态与线程关联

不要羡慕鄙人的英语,因为。。我是google翻译的…(咳咳)

这里只是摘了一段代码,从上面暴露的方法可以看到,提供了set,get方法,很明显就能看出来,set方法时,key是this,也就是当前的ThreadLocal对象,value就是传递进来的值,而最终是存储到哪呢, 一个叫ThreadLocalMap的对象,追踪一下,发现它其实是ThreadLocal的静态内部类:

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private void set(ThreadLocal<?> key, Object value) {
            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();
        }
	}
}

复制代码

Set方法

可以看到内部再维护了一个静态Entry类继承弱引用,所以上面所说的key,ThreadLocal对象其实是咦弱引用的形式存储的,这样也有益于GC回收,防止内存泄漏,我们先来看set方法:

  • 通过key的哈希码和数组长度,计算出存储元素的下标,这点应该很类似于HashMap中的找数组下标的方式。
  • 找到下标之后,一个循环,从i开始往后一直遍历到数组最后一个Entry,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
  • 如果找到下标为空的元素,跳出循环,将key和value,设置进去,填满该下标元素位置,同时size++,如果超过阈值,重新hash
private void rehash() {
           //清理一次旧的数据
            expungeStaleEntries();
           //如果当前size大于3/4的阈值,就进行扩容
            if (size >= threshold - threshold / 4)
                resize();
        }
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //将长度扩容到之前的2倍
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    //取出ThreadLocal对象
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        //如果不为空,类似上面的循环,一直找到一个没有使用的位置,在						空节点上塞入Entry
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            setThreshold(newLen);
            size = count;
            table = newTab;
        }

复制代码

大部分注释,其实都是根据里面的英文注释翻译过来的,所以想了解的可以静下心来好好的翻一翻源码,相信我,你会有意外的收获。

Get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
       	//通过当前线程,获取ThreadLocalMap,如果不为空,返回value,否则走初始化流程
        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;
    }
	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	//初始化,设置阈值位int值16
            table = new Entry[INITIAL_CAPACITY];
        	//计算数组下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
 	//阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希
     private void setThreshold(int len) {
        threshold = len * 2 / 3;
      }
复制代码
  • 从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,否则进入初始化
  • 初始化,设置数组初始长度,阈值等等参数

原文 

https://juejin.im/post/5c77a0dfe51d45374276fab9

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 带你揭秘神秘的ThreadLocal

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址