一、背景

最近有人问我ThreadLocal是如何做到在每个线程中的值都是隔离的,此处写篇文章来简单记录下。

二、ThreadLocal解决的问题

  1. 该数据属于该线程Thread自身,别的线程无法对其影响。(需要注意:需要调用ThreadLocal的remove方法)
  2. 不存在线程安全问题。(因为ThreadLocal类型的变量只有自身的线程可以访问,所以这点是成立的。)

比如:

用户登录成功后,需要将登录用户信息保存起来,以方便在系统中的任何地方都可以使用到,那么此时就可以使用ThreadLocal来实现。例如:Spring Security中的ThreadLocalSecurityContextHolderStrategy类。

三、如何创建一个ThreadLocal实例

private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();

ThreadLocal的实例推荐使用private static final来修饰。

四、ThreadLocal如何做到线程变量隔离

1、理解3个类

  1. ThreadLocal: 此类提供了一个简单的set,get,remove方法,用于设置,获取或移除 绑定到线程本地变量中的值。

  2. ThreadLocalMap: 这是在ThreadLocal中定义的一个类,可以简单的将它理解成一个Map,不过它的key是WeakReference弱引用类型,这样当这个值没有在别的地方引用时,在发生垃圾回收时,这个map的key会被自动回收,不过它的值不会被自动回收。

    static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
    // key 弱引用
    super(k);
    // 值强引用
    value = v;
    }
    }
  3. Thread:这个是线程类,在这个类中存在一个threadLocals变量,具体的类型是ThreadLocal.ThreadLocalMap

2、看下set方法是如何实现的

public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取绑定到这个线程自身的 ThreadLocalMap,这个ThreadLocalMap是从Thread类的`threadLocals`变量中获取的
ThreadLocalMap map = getMap(t);
if (map != null) {
// 向map中设置值,key为 ThreadLocal 对象的实例。
map.set(this, value);
} else {
// 如果map不存在,则创建出来。
createMap(t, value);
}
}

通过上方的代码,我们可知: 当我们向ThreadLocal中设置一个值,会经过如下几个步骤:

  1. 获取当前线程Thread
  2. 获取当前线程的 ThreadLocalMap 对象。
  3. ThreadLocalMap中设置值,key为ThreadLocal对象,值为具体的值。

3、看看 get 方法如何实现

public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取这个线程自身绑定的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// this是ThreadLocal对象,获取Map中的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取具体的值
T result = (T)e.value;
return result;
}
}
// 设置初始值
return setInitialValue();
}

从上方的get 和 set 方法可以得知,通过往ThreadLocal对象中设置值或获取值,其实是最终操作到Thread对象中的threadLocals字段中,而这个字段是Thread自身的,因此做到了隔离。

五、ThreadLocalMap中的hash冲突是如何处理的

1、ThreadLocal对象的hash值是怎样的

private final int threadLocalHashCode = nextHashCode();
// 该 ThreadLocal 对象自身的hash code值
private final int threadLocalHashCode = nextHashCode();
// 从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每次递增固定的值
private static final int HASH_INCREMENT = 0x61c88647;
// hash code 值计算
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

从上方的代码中可以,ThreadLocal类在实例化出来之后,它的hash code值(threadLocalHashCode)就是固定的,即使ThreadLocal调用了set方法,设置了别的值,它的hash code值也不会发生变化。

此字段threadLocalHashCodeThreadLocal对象的hash值,在ThreadLocalMap中需要用到这个hash值。

2、解决hash冲突

ThreadLocalMap解决hash冲突的办法很简单。就是通过线性探测法。如果发生了冲突,就去找数组后面的可用位置。具体看上图。演示的是A和B 2个ThreadLocal对象,然后发生了冲突,A和B存在的位置在那个地方。

六、ThreadLocal内存泄漏

ThreadLocal为什么会存在内存泄漏呢?

这是因为ThreadLocalMap中的keyWeakReference类型,也就是弱引用类型,而弱引用类型的数据在没有外部强引用类型的话,在发生gc的时候,会自动被回收掉。注意: 此时是key被回收了,但是value是没有回收的。因此在ThreadLocalMap中的Entry[]中可能存在keynull,但是value是具体的值的对象,因此就发生了内存泄漏。

解决内存泄漏:

当我们使用完ThreadLocal对象后,需要在适当的时机调用ThreadLocal#remove()方法。 否则就只有等Thread自动退出才能清除,如果是使用了线程池,Thread会重用,清除的机会就更难。

ThreadLocal的简单理解的更多相关文章

  1. 对ThreadLocal的一些理解

    ThreadLocal也是在面试过程中经常被问到的,本文主要从以下三个方面来谈对ThreadLocal的一些理解: ThreadLocal用在什么地方 ThreadLocal一些细节 ThreadLo ...

  2. ThreadLocal原理简单刨析

    ThreadLocal原理简单刨析 ThreadLocal实现了各个线程的数据隔离,要知道数据是如何隔离的,就要从源代码分析. ThreadLocal原理 需要提前说明的是:ThreadLocal只是 ...

  3. git的简单理解及基础操作命令

    前端小白一枚,最近开始使用git,于是花了2天看了廖雪峰的git教程(偏实践,对于学习git的基础操作很有帮助哦),也在看<git版本控制管理>这本书(偏理论,内容完善,很不错),针对所学 ...

  4. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  5. [转]简单理解Socket

    简单理解Socket 转自 http://www.cnblogs.com/dolphinX/p/3460545.html  题外话 前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公 ...

  6. Js 职责链模式 简单理解

    js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = funct ...

  7. Deep learning:四十六(DropConnect简单理解)

    和maxout(maxout简单理解)一样,DropConnect也是在ICML2013上发表的,同样也是为了提高Deep Network的泛化能力的,两者都号称是对Dropout(Dropout简单 ...

  8. Deep learning:四十二(Denoise Autoencoder简单理解)

    前言: 当采用无监督的方法分层预训练深度网络的权值时,为了学习到较鲁棒的特征,可以在网络的可视层(即数据的输入层)引入随机噪声,这种方法称为Denoise Autoencoder(简称dAE),由Be ...

  9. 简单理解dropout

    dropout是CNN(卷积神经网络)中的一个trick,能防止过拟合. 关于dropout的详细内容,还是看论文原文好了: Hinton, G. E., et al. (2012). "I ...

随机推荐

  1. Blazor WebAssembly 渐进式 Web 应用程序 (PWA) 使用 LocalStorage 离线处理数据

    原文链接:https://www.cnblogs.com/densen2014/p/16133343.html Window.localStorage 只读的localStorage 属性允许你访问一 ...

  2. 谈谈.NET Core下如何利用 AsyncLocal 实现共享变量

    前言 在Web 应用程序中,我们经常会遇到这样的场景,如用户信息,租户信息本次的请求过程中都是固定的,我们希望是这种信息在本次请求内,一次赋值,到处使用.本文就来探讨一下,如何在.NET Core 下 ...

  3. transform动画

    1. html 结构 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  4. JuiceFS 缓存预热详解

    缓存预热是一个比较常见的概念,相信很多小伙伴都有所了解.对于 JuiceFS 来说,缓存预热就是将需要操作的数据预先从对象存储拉取到本地,从而获得与使用本地存储类似的性能表现. 缓存预热 JuiceF ...

  5. GO语言学习——Go语言基础之流程控制一

    Go语言基础之流程控制 if else(分支结构) package main import "fmt" // if条件判断 func main(){ // age := 19 // ...

  6. SSM框架中返回的是字符串还是页面跳转的问题

    如果你在控制器前的注解是@RestController的话,将返回controller方法指定的String值,@RestController注解相当于@ResponseBody和@Controlle ...

  7. python学习-Day22

    目录 今日内容详细 hashlib加密模块 什么是加密 加密算法 加密的使用 基本使用 指定算法(md5) 将明文数据传递给算法对象 获取加密之后的密文数据 加密补充 加盐处理 动态加盐 加密应用场景 ...

  8. SpringBoot 读取配置文件数据

  9. Prometheus监控实战应用

    目录 1.Prometheus的主要特征及概述 2.普罗米修斯原理架构图 3.下载安装启动Prometheus 4.web客户端操作 5.默认图像 6.目录解释 7.配置文件 8.监控指标 8.1.监 ...

  10. C++实例2--职工管理系统

    职工管理系统 1.  头文件 1.1 workerManager.h 系统类 1 #pragma once // 防止头文件重复包含 2 #include<iostream> // 包含输 ...