面试高频 ThreadLocal类详解

简介: 面试高频 ThreadLocal类详解

前言

本文将从ThreadLocal的作用以及源码来进行对其的详细分析,包括内存泄漏以及其的本质原因等等

首先是ThreadLocal能干啥的问题,我将其总结为三点

1.传递数据:实现在公共组件中传递信息的需求

2.线程隔离:实现每个线程都是不相互影响的

3.线程并发:在多线程场景下完成需求

常用方法

常用的就是四个方法

1.构造方法

2.set方法

将变量绑定到线程上

3.get方法

获取绑定的线程

4.remove方法

移除绑定的变量(entry)

结构分析

我们这里直接考虑jdk8之后的结构而不考虑之前的结构了

这里是每一个thread线程持有一个threadlocalmap

是一个map结构,里面存放的是一个个entry

entry的key是threadlocal对象

entry的值是线程绑定的变量值

这样设计的优势:

1. 每个map存储的entry数量变少了 因为之前是由thread的数量决定的 现在的entry是由threadlocal决定

2.当thread销毁的时候threadlocalmap也会随之销毁,减少内存的使用

ThreadLocal和synchronized的区别与联系

我们知道解决多线程的并发问题,最常见的思想就是加锁

我们很轻易的就能想到Synchronized关键字

但是使用synchronized关键字加锁会导致程序的性能降低

主要的是两者的思想不同

synchronized主要思想使用时间换空间,侧重于多线程程序的同步性

threadlocal主要是用空间换时间

主要侧重于线程之间的隔离

一个简单的案例分析

假设我们这里需要做一个经典的转账接口,我们故意设置a扣费之后进行报错,使得b不能收到转账,这样a的转账就不翼而飞了,我们希望使用这两个操作来解决这个问题

思路1:

使用事务和加锁来解决问题

我们将这转账一系列操作使用事务来完成,但是我们就得注意这里服务分层了

业务层和数据层之间的连接必须要保持一致,这样就多了一个传参以及加锁的性能损耗

思路2:

使用threadlocal来操作

这样既不需要多付出性能的代价,也降低的层之间的耦合性,虽然增加了空间损耗,但是我们知道对于现在来说空间是比较不值钱的,相对来说效果更优.

源码分析

下面我们对几个常用方法进行源码的解析

set方法

先获取线程对应的threadlocalmap

获取到了就直接设置值

获取不到就创建一个新的map并设置初始值

get方法

首先获取当前线程,根据当前线程获取一个map

如果map不为空,就根据threadlocal找到对应的entry返回value,

如果map为空则通过initialValue函数获取初始值value

然后根据threadlocal的引用和value创建一个新的map

remove方法

如果发现对应的entry,流程是先获取到thread对应的threadlocalmap,存在就移除entry

不存在就啥也不干

ThreadLocalMap的分析

threadlocalMap是threadlocal的一个静态内部类

threadlocalmap是threadlocal独立实现的,没有实现任何的map接口等,以下是他的结构图

他的entry也是自己定义的,没有继承或者实现某些接口

下面我们来看看entry的内部定义源码

我们发现这里的entry是实现的弱引用,为什么要使用弱引用呢,我们在后面继续聊

内存泄露

首先我们谈谈内存泄露的定义

这里大致有两种定义

一种是内存溢出(用户分配的内存的空间不够用)的原因

可能可以使用调大设置内存空间来解决问题

另一种是系统的总内存不足以使用(满足不了程序的需求)

网上有想法说:内存泄露是因为使用若引用的缘故

其实不然,我们下面开始分别谈谈使用弱引用和强引用之间的区别,最后谈谈内存泄露的真实原因到底是什么.

强引用   被强引用引用的时候,不会被gc回收

弱引用  被弱引用引用的时候,被gc发现即回收

使用弱引用

这里如果我们不使用remove方法,只要线程还是存在的,这里指向entry的引用就会存在,entry仍然不会删除

虽然这里的threadlocal被置空了,但是也只是threadlocal被回收了,而entry不会被回收

强引用

仍然是始终有强引用链指向entry,不会被系统gc回收

本质原因

其本质原因主要和引用的强弱与否用处不大

本质上是thread和entry的生命周期是一样的,使用弱引用相对来说更加有保障一点

因为在调用get set方法等的时候,如果发现这里的entry为空就会自动给置空

这样可以一定程序上防止内存泄露的问题

解决哈希冲突

我们知道threadlocalmap是一个map本质上还是会出现hash冲突的情况

那么我们会以什么方式来解决hash冲突的呢?

我们从源码上来分析问题

这里的hash计算就是上述代码中i的计算

我们发现他还有一个函数和一个INITIAL_CAPICITY

下面我们再往里面走一步看看

这里使用了原子类的getAndAdd方法每次获取并加上一个十六进制的值

这个值主要和斐波那契数列那个黄金分割值有关,目的是让hash值均匀的分布在2的n次方的数组中,可以用来解决hash冲突问题

这里还与上了一个capacity-1其目的是让其最后生成的hash值不会溢出

因为下面使用的是线性探测的方式来解决hash冲突

相关文章
|
4天前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
37 0
|
4天前
|
存储 Java 中间件
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
|
3天前
|
设计模式 算法 Java
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
|
4天前
|
存储 缓存 安全
面试被问ThreadLocal要怎么回答?
ThreadLocal是Java中为每个线程提供独立变量副本的类,避免多线程同步。它内部维护ThreadLocalMap,存在内存泄漏风险,使用后需调用remove()。常用于记录用户请求数据、事务处理、日志记录和连接池管理等场景。注意内存泄漏和性能影响,以及与InheritableThreadLocal和同步代码块的区别。【5月更文挑战第7天】
|
4天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
24 0
|
4天前
|
安全 Java
【JAVA面试题】什么是对象锁?什么是类锁?
【JAVA面试题】什么是对象锁?什么是类锁?
|
4天前
|
存储 安全 Java
多线程编程常见面试题讲解(锁策略,CAS策略,synchronized原理,JUC组件,集合类)(下)
多线程编程常见面试题讲解(锁策略,CAS策略,synchronized原理,JUC组件,集合类)(下)
44 0
|
4天前
|
存储 安全 Java
多线程编程常见面试题讲解(锁策略,CAS策略,synchronized原理,JUC组件,集合类)(上)
多线程编程常见面试题讲解(锁策略,CAS策略,synchronized原理,JUC组件,集合类)
38 0
|
4天前
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
20 0
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
|
4天前
|
存储 编译器 程序员
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
113 0