首页 > 编程语言 >Java详解ThreadLocal

Java详解ThreadLocal

时间:2023-07-20 10:36:24浏览次数:34  
标签:Java tab get ThreadLocal 详解 key hash null

threadlocal

1 基础

现象:threadlocal作为一个全局变量,在不同的线程去get的时候能够获取不同的值。

应用场景:

  • SimpleDateFormat线程不安全,每个线程都要用,new太多,放到threadlocal中线程池可反复使用。
  • 一个请求链路很长,经过数个服务,每次都要放到参数带着。改为直接放到threadlocal作为上下文。(每个线程独立的上下文)

原理:ThreadLocal对象本身其实不存储内容,而是Thread对象有个ThreadLocalMap来进行存储,ThreadLocal作为这个map的key,运行get方法,实际是map.get(threadlocal)

2 使用习惯

线程池与ThreadLocal配合使用的时候,一定注意线程是回收的,Thread对象还在,因而ThreadLocalMap还在,因而里面的key-value都没有被销毁。这样轻则是引起内存泄漏,一直没有清理这部分内容。严重的则会因为再次get到线程上一世的内容,导致错误。

所以如果threadlocal不再使用了,一定记得及时remove掉,防止不必要的麻烦。

上面场景1其实是利用了不被清理这一点进行了复用。

下面基于jdk1.8

3 弱引用

/**
 * 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;
    }
}

entry弱引用了key,也就是说key如果没有被其他对象强引的话,下次gc就被删掉了。这个有啥用呢?

参考20.09中ThreadLocal的讲解吧,这里之前写错了。

4 内存结构

ThreadLocalMap是采用了数组的存储结构,每个元素就是entry本身,而不是链表红黑树。

虽然上述情况很少出现,但是array却很好的避免了这一点。

数组会出现冲突,是怎么解决的?

如果冲突就往后一位,如果后面一位不为空,就继续往后,直到找到空位子。

可能会出现hash为3,4,3的元素abc分别存储最后存入的样子是abc(ac的hash都是3却不相邻),这也是为啥get的时候需要一直找到null为止。

5 get set remove清理key为null的entry

先说get的时候,直接找arr[hash(key)],如果就是key所在的entry那就返回:

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

如果没找到就数组往下找,一直找到null元素,都没找到的话就返回null:

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

!! 注意,get过程中找到k==null的需要进行expungeStaleEntry(清除陈旧的entry),将k=null的当前这条entry的value和entry本身清理掉。然后还需要从当前位置向后一直到null的所有元素都进行重排。重排可以定义为:

元素尽量往他本来应该在的hash(element)这个位置移动的过程叫做重排

先删除k=null的这个元素,然后后面的重排的过程叫删除并重排

例如hash为4、3的bc分别在4和5位置,这是有可能的因为3位置可能之前有元素,后来key成了null如下:

数组:
xxx|xxx|xxx|null-v|b-v|c-v|null|....
get(某个hash为3的元素)触发了重排:
xxx|xxx|xxx|c-v|b-v|null|....

上面重排过程c-v移动到了b-v前面,因为b的hash本就是4所以保持不动,c的hash是3,尽量前移发现3位置是null就直接到3了。

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

remove和get一样,还少了初始化的步骤。

set

set是最复杂的,假设hash(key)=x,同样去找table[x]。

  • 如果找到null,就直接令table[x]=<key,value>。
  • 如果找到e,且e.k==key,则直接更新e.v=value。
  • 如果找到e,且e.k!=key,则x++。

如果上述过程中找到了e.k==null,这里和get不一样,假设这个位置下标是y(y>=x)。set的时候是先去找一数z。括号里先不看,方便理解(先向左,找到null为止,找下标最小的一个e.k==null的元素,这个下标就是z。如果没有找到,则)向后找找到第一个e.k==null的元素的下标就是z了。

也就是找到k=null的再往后找下一个k=null的。

在向后找的过程中,需要一边判断e.k是不是刚好就是key。如果是的话,循环就可以退出了,将y位置直接设置为[key,value],将找到的这个位置清除,令z=当前这个位置。然后在z往后运行log(size)次【删除并重排】。

set的清理不是一轮而是log2(table.length)-1轮,清理范围更大; set的key本身找到的话会被跳到尽可能最靠前的地方(y位置)。

下面所有的k的hash都是x,思考k4.set(vv4)过程。

x        x+1    x+2    x+3
|null-v1|k2-v2|k3-v3|k4-v4|..
️
|k4-vv4 |k2-v2|k3-v3|null |..

如果按照get的流程,则应该是k2,3,4各自前移一步,此处set策略不同,仔细体会下。

set清理比get要多,但都不是完全清理掉k=null的。

为啥要清理k=null的,因为用不到了,get不到了,所以没有存在的意义,不清理也是内存垃圾。

标签:Java,tab,get,ThreadLocal,详解,key,hash,null
From: https://blog.51cto.com/u_16007752/6782954

相关文章

  • Java开发工具MyEclipse发布v2023.1.2,今年第二个修复版!
    MyEclipse一次性提供了巨量的Eclipse插件库,无需学习任何新的开发语言和工具,便可在一体化的IDE下进行JavaEE、Web和PhoneGap移动应用的开发;强大的智能代码补齐功能,让企业开发化繁为简。MyEclipsev2023.1.2官方正式版下载更新日志如下:v2023.1.2是MyEclipse2023的一个小错误修......
  • ORA-01555错误详解
    ORA-01555错误详解 ORA-01555(快照过旧)问题让很多人感到十分头痛。最近我们的生产系统上也报出了ORA-01555错误。就结合这次案例将ORA-1555问题作个案例分析,并浅析产生原因和各种解决办法。如果要了解1555错误产生的原因,就需要知道ORACLE的两个特性:一致性读(ConsistentGet)和延......
  • Java各种注解
    @EnableConfigurationProperties({MinIOConfigProperties.class})将@ConfigurationProperties标记的类作为Bean注入到容器中,也可以在原来的@ConfigurationProperties上继续加@Component,相当于吧@ConditionalOnClass(FileStorageService.class)表示有这个类时才会注入spring容器......
  • Javascript和jQuery有什么不同?
     Javascript和jQuery都是前端开发必备的语言和框架,但他们之间有很多不同。下面我们来详细的分析一下。Javascript是一种脚本语言,可以在浏览器端直接运行。它的语法简单,可以操作HTML和CSS,实现动态效果,如表单验证、动态创建元素等。Javascript的核心是ECMAScript标准,它定义了语......
  • JavaScript和Java如何进行通信
     JavaScript和Java是两种不同的编程语言,但是它们可以通过一些技术手段进行通信。在Web开发领域,JavaScript和Java的交互性十分重要,因为它们可以实现复杂的交互操作和数据处理,为Web应用程序带来更好的用户体验。一、Java与JavaScript的通信方式1.JavaappletJavaapplet是一......
  • JavaScript汉字转码原理解析
     JavaScript是一种高级编程语言,广泛应用于Web、移动应用开发等领域。其中,涉及到的汉字编码问题一直备受关注。本篇文章将从JavaScript汉字转码的原理解析入手,为读者深入剖析这一现象。什么是汉字编码?汉字编码是指计算机用二进制形式来表示中文字符的方式。由于计算机只能识别......
  • JavaScript函数中嵌套函数的使用方法及技巧
     在JavaScript编程中,函数是用来封装可重用代码的一种重要工具。但是,有时候在函数内部需要创建另一个函数来完成一些特定的功能。这种在函数内部定义的函数被称为嵌套函数。本文将讨论JavaScript函数中嵌套函数的使用方法及技巧。1.嵌套函数的定义在JavaScript中,嵌套函数可以......
  • JavaScript函数重载实现方法
     在编写JavaScript代码时,我们常常遇到需要编写多个名称相同但参数类型不同的函数的情况。这时,我们可以使用JavaScript函数重载来实现这一功能。函数重载是指在同一个作用域内定义多个同名函数,但参数类型和数量各不相同。在调用函数时,根据传入的参数类型和数量,自动匹配对应的函......
  • 如何使用AJAX实现JavaScript文件下载
    如何使用AJAX实现JavaScript文件下载AJAX(AsynchronousJavaScriptandXML)技术是一种通过JavaScript异步地向服务器发送请求,更新网页内容的技术。在Web开发中,AJAX是非常常用的技术之一,它可以使网页交互更加流畅,也可以实现一些复杂的交互操作。本文将介绍如何使用AJAX实现JavaScri......
  • java 百分比
    /**百分比参1/参2**/StringgetPercent(Strings1,Strings2){StringsResult="";if(StrUtil.isBlank(s2)||s2.equals("0")||Integer.valueOf(s2)==0)return"0%";java.text.NumberFormatnumberFor......