• 技术文章 >Java >java教程

    ThreadLocal的实现原理的分析介绍(附代码)

    不言不言2019-02-16 13:37:47转载959
    本篇文章给大家带来的内容是关于ThreadLocal的实现原理的分析介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

    ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。

    本篇从源码方面分析ThreadLocal的实现原理。

    先看一下ThreadLocal类图结构

      

    SuppliedThreadLocal主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。

    ThreadLocalMap是ThreadLocal的静态内部类,也是实际保存变量的类。

    Entry是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。

    下面以一张图简要说明Thread,ThreadLocal,ThreadLocalMap和Entry的关系。

      

    说明一下上图:

    1. 一个Thread拥有一个ThreadLocalMap对象

    2. ThreadLocalMap拥有一个Entry数组

    3. 每个Entry都有k--v

    4. Entry的key就是某个具体的ThreadLocal对象

    下面分析主要方法。

    1、set()

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

    这里可以看出:一个Thread只拥有一个ThreadLocalMap对象;具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。

    再看看ThreadLocalMap的set()方法:

    private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1); // 1
    
                for (Entry e = tab[i];  // 2
                     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); // 3
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                    rehash();
            }

    1. 通过key的hashCode与数组容量 -1 取模,计算数组index

    2. 从当前index开始遍历,清除key为null的无效Entry

    3. 将K-V封装为Entry,并放入数组

    4. 判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。

      看看扩容的resize()方法:

    private void resize() {
                Entry[] oldTab = table;
                int oldLen = oldTab.length;
                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();
                        if (k == null) {
                            e.value = null; // Help the GC
                        } else {
                            int h = k.threadLocalHashCode & (newLen - 1);
                            while (newTab[h] != null)
                                h = nextIndex(h, newLen);
                            newTab[h] = e;
                            count++;
                        }
                    }
                }
    
                setThreshold(newLen);
                size = count;
                table = newTab;
            }

    这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。

    2、get()

    ThreadLocal的get()方法如下:

    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();
        }

    ThreadLocalMap的getEntry()方法如下:

    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1); // 1
                Entry e = table[i];
                if (e != null && e.get() == key) // 2
                    return e;
                else
                    return getEntryAfterMiss(key, i, e); //3
            }
    
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) { //4
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e;
                    if (k == null)
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }

    1. 计算index

    2. 当前index上的Entry不为空且key相同,直接返回

    3. 否则去相邻index寻找

    4. 循环查找,发现无效key就清除。找到就结束循环。

    3、remove()

    public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    private void remove(ThreadLocal<?> key) {
                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)]) {
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }

    处理方式和查找保存类似,删除对应Entry后都会去除key为null的无效元素。

    注意

    static class Entry extends WeakReference<ThreadLocal<?>> {}

    ThreadLocal可能存在OOM问题。因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。

    在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。

    以上就是ThreadLocal的实现原理的分析介绍(附代码)的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:博客园,如有侵犯,请联系admin@php.cn删除
    专题推荐:ThreadLocal
    上一篇:​JVM,JRE,JDK三者的简单总结 下一篇:SpringBoot加载子模块配置文件的详细介绍(代码示例)
    大前端线上培训班

    相关文章推荐

    • Java本地线程(ThreadLocal)• 详解java中ThreadLocal本地线程和同步机制的比较实例• Java中ThreadLocal内存泄露的代码实例分享(图)• java中正确使用ThreadLocal的实例代码分析

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网