Java中的ThreadLocalMap源码解读

 更新时间:2023年09月26日 10:30:00   作者:回家放羊吧  
这篇文章主要介绍了Java中的ThreadLocalMap源码解读,ThreadLocalMap是ThreadLocal的内部类,是一个key-value数据形式结构,也是ThreadLocal的核心,需要的朋友可以参考下

概述

ThreadLocalMap是ThreadLocal的内部类,是一个key-value数据形式结构,也是ThreadLocal的核心。

ThreadLocalMap中数据是存储在Entry类型数组的table中的,Entry继承了WeakReference(弱引用),注意key是弱引用,vlaue不是。

源码解读

1.成员变量

/**
	 * 初始容量
	 */
	private static final int INITIAL_CAPACITY = 16;
	/**
	 * ThreadLocalMap数据真正存储在table中
	 */
	private Entry[] table;
	/**
	 * ThreadLocalMap条数
	 */
	private int size = 0;
	/**
	 * 达到这个大小,则扩容
	 */
	private int threshold; // 默认为0

2.threadLocalHashCode

private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
 * The difference between successively generated hash codes - turns
  * implicit sequential thread-local IDs into near-optimally spread
  * multiplicative hash values for power-of-two-sized tables.
  */
 private static final int HASH_INCREMENT = 0x61c88647;
 /**
  * Returns the next hash code.
  */
 private static int nextHashCode() {
     return nextHashCode.getAndAdd(HASH_INCREMENT);
 }

HASH_INCREMENT = 0x61c88647是一个魔法数,可以减少hash冲突,通过nextHashCode.getAndAdd(HASH_INCREMENT)方法会转化为二进制数据,主要作用是增加哈希值,减少哈希冲突

3.构造函数

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //初始化table数组,INITIAL_CAPACITY默认值为16
        table = new Entry[INITIAL_CAPACITY];
        //key和16取得哈希值
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //创建节点,设置key-value
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        //设置扩容阈值
        setThreshold(INITIAL_CAPACITY);
}

4.set

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) {
                //如果key是相同,则替换,并return
                e.value = value;
                return;
            }
            if (k == null) {
                //e!=null,key==null,因为key是弱引用,所以key已经被gc回收了,replaceStaleEntry方法就是用来解决内存泄露问题
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                   int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        Entry e;
        int slotToExpunge = staleSlot;
        //prevIndex是指针向前,寻找前面过期数据
        for (int i = prevIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;
        //向后寻找key相同的数据
        for (int i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            if (k == key) {
                e.value = value;
                //通过和过期的slot进行交换,维护哈希表顺序
                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;
                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                //清除过期slot
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }
             if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }
        // 如果key并没有在map中出现过,则直接创建
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);
        //如果还有其他过期slot,则清除
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }
    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        do {
            i = nextIndex(i, len);
            Entry e = tab[i];
            if (e != null && e.get() == null) {
                n = len;
                removed = true;
                i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);
        return removed;
    }
    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        // 删除下标为staleSlot的slot
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;
        // 重新哈希,直到遇到null
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            //如果key==null,说明已经被回收
            if (k == null) {
                //Entry设置为null,size减一
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                //重新进行hash计算
                int h = k.threadLocalHashCode & (len - 1);
                //如果计算的位置和从前位置不一致
                if (h != i) {
                    tab[i] = null;
                    //扫描到null,将值放入
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        return i;
    }
    private void rehash() {
            expungeStaleEntries();
            //如果当前size大于法制的四分之三,则扩容
            if (size >= threshold - threshold / 4)
                resize();
    }
    /**
     * 全局清理
     */
    private void expungeStaleEntries() {
        Entry[] tab = table;
        int len = tab.length;
        for (int j = 0; j < len; j++) {
            Entry e = tab[j];
            if (e != null && e.get() == null)
                expungeStaleEntry(j);
        }
    }

set方法首先根据key计算存储位置 如果计算出来的下标不为空,会进入循环,循环内如果key相同,则直接替换,如果key被回收,则调用replaceStaleEntry方法清除,并且在该方法中设置value。 如果计算出来的下标为空,则直接设置值,并在最后通过cleanSomeSlots清除过期key和确定是否通过rehash扩容。

5.getEntry

 	private Entry getEntry(ThreadLocal<?> key) {
        //计算下标位置
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        //没有hash冲突,entry存在,并且key未被回收
        if (e != null && e.get() == key)
            return e;
        else
            //hash冲突,通过线性探测查找,可能查询到
            return getEntryAfterMiss(key, i, e);
    }
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
		//循环查找,直到为null
        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;
    }

getEntry是根据ThreadLocal获取ThreadLocalMap中某个值的,如果存在哈希冲突则通过getEntryAfterMiss方法线性探测查找

6.remove

private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        //如果threadLocalHashCode计算出的下标找到的key和传入key不同,则证明出现哈希冲突,则循环向下查找
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            //如果key相同
            if (e.get() == key) {
                //删除当前Entry
                e.clear();
                //清理
                expungeStaleEntry(i);
                return;
            }
        }
    }

总结

1.ThreadLocalMap.Entry继承了WeakReference,实现了弱引用,提高了垃圾回收的效率。

2.ThreadLocalMap可能存在内存泄露,因为key被回收后,但是value依然和Entry存在强引用关系,所以使用完进行remove是一个很好的习惯,可以避免内存泄露。

到此这篇关于Java中的ThreadLocalMap源码解读的文章就介绍到这了,更多相关ThreadLocalMap源码解读内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中生成随机数的4种方式与区别详解

    Java中生成随机数的4种方式与区别详解

    生成随机数是我们日常开发经常会遇到的一个功能,这篇文章主要给大家介绍了关于Java中生成随机数的4种方式与区别、应用场景的相关资料,4个方式分别是Random、ThreadLocalRandom、SecureRandom以及Math,需要的朋友可以参考下
    2021-06-06
  • SpringBoot使用SOFA-Lookout监控的方法

    SpringBoot使用SOFA-Lookout监控的方法

    本文介绍SpringBoot使用蚂蚁金服SOFA-Lookout配合Prometheus进行监控,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • 带你了解Java数据结构和算法之链表

    带你了解Java数据结构和算法之链表

    这篇文章主要为大家介绍了Java数据结构和算法之链表 ,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • 创建网关项目(Spring Cloud Gateway)过程详解

    创建网关项目(Spring Cloud Gateway)过程详解

    这篇文章主要介绍了创建网关项目(Spring Cloud Gateway)过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Spring依赖注入的三种方式详解

    Spring依赖注入的三种方式详解

    这篇文章主要给大家介绍了三种Spring依赖注入的方式, settter方法注入,构造器注入以及变量(filed) 注入这三种方式,文章通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-11-11
  • Spring Cloud OpenFeign模版化客户端搭建过程

    Spring Cloud OpenFeign模版化客户端搭建过程

    OpenFeign是一个显示声明式的WebService客户端。使用OpenFeign能让编写Web Service客户端更加简单,这篇文章主要介绍了Spring Cloud OpenFeign模版化客户端,需要的朋友可以参考下
    2022-06-06
  • spring整合cxf框架实例

    spring整合cxf框架实例

    下面小编就为大家带来一篇spring整合cxf框架实例。小编觉得挺不错的。现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Intellij idea远程debug连接tomcat实现单步调试

    Intellij idea远程debug连接tomcat实现单步调试

    这篇文章主要介绍了Intellij idea远程debug连接tomcat实现单步调试,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Struts2学习教程之自定义类型转换器的方法

    Struts2学习教程之自定义类型转换器的方法

    类型转换器的作用是将请求中的字符串或字符串数组参数与action中的对象进行相互转换。下面这篇文章主要给大家介绍了关于Struts2学习教程之自定义类型转换器的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-05-05
  • java string类的常用方法详细介绍

    java string类的常用方法详细介绍

    在开发过程中经常会使用到java string类的方法,本文将以此问题进行详细介绍
    2012-11-11

最新评论