ThreadLocal的实现原理是什么

这篇文章主要介绍了ThreadLocal的实现原理是什么,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。

创新互联建站是网站建设专家,致力于互联网品牌建设与网络营销,专业领域包括成都网站设计、网站建设、电商网站制作开发、微信平台小程序开发、微信营销、系统平台开发,与其他网站设计及系统开发公司不同,我们的整合解决方案结合了恒基网络品牌建设经验和互联网整合营销的理念,并将策略和执行紧密结合,且不断评估并优化我们的方案,为客户提供全方位的互联网品牌整合方案!

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

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

先看一下ThreadLocal类图结构

 ThreadLocal的实现原理是什么

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

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

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

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

 ThreadLocal的实现原理是什么

说明一下上图:

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

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

感谢你能够认真阅读完这篇文章,希望小编分享ThreadLocal的实现原理是什么内容对大家有帮助,同时也希望大家多多支持创新互联,关注创新互联行业资讯频道,遇到问题就找创新互联,详细的解决方法等着你来学习!


网页题目:ThreadLocal的实现原理是什么
当前URL:http://hbruida.cn/article/peeedg.html