概述

ThreadLocal 意为本地线程变量,即该变量只属于当前线程,对其他线程隔离

我们知道,一个普通变量如果被多线程访问会存在存在线程安全问题,这时我们可以使用 Synchronize 来保证该变量某一时刻只能有一个线程访问,从而解决并发安全问题

但如果这个变量并不需要被共享,那么就可以使用 ThreadLocal 为每个线程提供一个完全独立的变量副本,每个线程只操作自身拥有的副本,彼此互不干扰

简而言之,Synchronized 用于线程间的数据共享,同步机制采用采用时间换空间的方式,而 ThreadLocal 则用于线程间的数据隔离,采用空间换时间的方式

ThreadLocal 使用

public class ThreadLocalTest {

  // 有个User对象需要在不同线程之间进行隔离访问,可以定义ThreadLocal如下
static ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); // 设置线程本地变量的内容
public void setUser(User user) {
userThreadLocal.set(user);
} public void getUser() {
// 获取线程本地变量的内容
User user = userThreadLocal.get();
user.setUsername(user.getUsername + "-" + Thread.currentThread().getName());
System.out.println(user.getUsername());
// 移除线程本地变量
userThreadLocal.remove();
}
}

测试代码如下

public class DemoTest {
public static void main(String[] args) {
ThreadLocalTest threadLocalTest = new ThreadLocalTest();
for(int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
User user = new User();
user.setUsername("小明");
threadLocalTest.setUser(user);
threadLocalTest.getUser();
});
thread.setName(String.valueOf(i));
thread.start();
}
}
}

ThreadLocal 原理

首先,Java 中的线程是一个 Thread 类的实例对象,对象可以定义私有的成员变量,这也是 ThreadLocal 能实现线程本地变量的基础

在 Thread 类中定义了一个 Map 类型的成员变量,用来保存该线程的所有本地变量

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap 的 Entry 的定义如下,key 为 ThreadLocal 对象,v 就是我们要在线程之间隔离的对象

static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

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

使用 set 方法赋值时,首先会获取当前线程 thread,并获取 thread 线程的 ThreadLocalMap 属性。如果 map 属性不为空,则直接更新 value 值,key 就是自身的 ThreadLocal,如果 map 为空,则实例化 threadLocalMap,并将 value 值初始化

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

使用 get 方法获取值,也是首先获取当前线程,再获取线程 ThreadLocalMap,如果 map 不为空就用自身 ThreadLocal 为 key 从 map 获取对应的 value

ThreadLocal::remove 方法的源码如下:

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}

remove 方法也是获取当前线程并获取 ThreadLocalMap,再将自身 ThreadLocal 为 key 对应的 value 移除

综合以上,我们知道 ThreadLocalMap 是线程的一个属性值,用来保存该线程的本地变量。ThreadLocal 能操作当前线程的 ThreadLocalMap,具体做法是以自身为 key 在 map 中存取值。因为每个线程的 ThreadLocalMap 都是独立的,所以每次使用 ThreadLocal 存取值都仅限于当前的线程,不会影响其他线程

ThreadLocal 内存泄露问题

内存泄露问题:指程序中动态分配的堆内存由于某种原因没有被释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢或者系统奔溃等严重后果。内存泄露堆积将会导致内存溢出

观察使用 ThreadLocal 时的内存布局

当 ThreadLocal Ref 被回收了,由于在 Entry 使用的是强引用,在 Current Thread 还存在的情况下就存在着到达 Entry 的引用链,无法清除掉 ThreadLocal 的内容,同时 Entry 的 value 也同样会被保留,也就是说使用了强引用会出现内存泄露问题

为此,ThreadLocal 在 Entry 使用了弱引用

static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
// Entry继承WeakReference,将key传入父级构造方法,从而形成弱引用
super(k);
value = v;
}
}

再观察使用软引用后的内存布局

当 ThreadLocal Ref 被回收,由于在 Entry 使用的是弱引用,因此在下次垃圾回收的时候就会将 ThreadLocal 对象清除

但由于 ThreadLocalMap 仍然存在 Current Thread Ref 这个强引用,Entry 中 value 的值仍然无法清除,还是存在内存泄露的问题,虽然当线程生命周期结束,Current Thread Ref 这个强引用也会随之消失,value 会在下一次垃圾回收被清除,但如果使用线程池,那么线程会被重新放回线程池等待复用,那么强引用就会一直存在。因此,我们在每次在使用完之后需要手动的 remove 掉 Entry 对象

ThreadLocal 本地线程变量详解的更多相关文章

  1. ThreadLocal本地线程变量的理解

     一般的Web应用划分为展现层.服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用.在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程.       ...

  2. Java 类 ThreadLocal 本地线程变量

    前言:工作中将要使用ThreadLocal,先学习总结一波.有不对的地方欢迎评论指出. 定义 ThreadLocal并不是一个Thread,而是Thread的局部变量.这些变量不同于它们的普通对应物, ...

  3. Java Concurrency - ThreadLocal, 本地线程变量

    共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量.Java API 提供了 ThreadLocal 来解决这个问题. 一个 ThreadLocal 作用的例子: imp ...

  4. mysql show variables系统变量详解

    mysql系统变量详解 mysqld服务器维护两种变量.全局变量影响服务器的全局操作.会话变量影响具体客户端连接相关操作. 服务器启动时,将所有全局变量初始化为默认值.可以在选项文件或命令行中指定的选 ...

  5. [Spark内核] 第36课:TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等

    本課主題 通过 Spark-shell 窥探程序运行时的状况 TaskScheduler 与 SchedulerBackend 之间的关系 FIFO 与 FAIR 两种调度模式彻底解密 Task 数据 ...

  6. Java性能分析之线程栈详解与性能分析

    Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...

  7. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  8. APP漏洞扫描器之本地拒绝服务检测详解

    APP漏洞扫描器之本地拒绝服务检测详解 阿里聚安全的Android应用漏洞扫描器有一个检测项是本地拒绝服务漏洞的检测,采用的是静态分析加动态模糊测试的方法来检测,检测结果准确全面.本文将讲一下应用漏洞 ...

  9. net core体系-web应用程序-4net core2.0大白话带你入门-5asp.net core环境变量详解

    asp.net core环境变量详解   环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的 ...

  10. Maya 常用环境变量详解

    Maya 常用环境变量详解 前言: Maya 的环境变量让用户可以很方便的自定义 Maya 的功能. 在 Maya 的 Help 帮助文档中有专门的一个章节< Environment Varia ...

随机推荐

  1. Java Springbool敏感词过工具类滤

    Java Springbool敏感词过工具类滤 1. 功能描述利用前缀树这种数据结构,设计并开发出敏感词过滤工具. 2. 构建敏感词表resource/sensitive-words.txt 3. 敏 ...

  2. 淘天Java一面,难度适中!(上篇)

    1.公司介绍 淘天集团是阿里巴巴集团全资拥有的业务集团,全球领先的科技商业公司. 淘天集团以淘宝 APP 为主要服务载体,构建国内国际供给.线上线下场景.远场近场履约相结合的商业矩阵,汇聚数十万全球和 ...

  3. this.$router 与this.$route的区别

    this.$router是Vue-Router的实例,需要导航到不同路由则用this.$router.push方法 this.$route为当前路由的跳转对象,包含当前路由的name.path.que ...

  4. 【Android】【外包杯】后台管理系统 | 进度day01

    外包杯官方提示:平台不要太大,只是一些小东西包括支付宝和微信小程序打开,无需安装口香糖,餐巾纸有一块屏幕,不需要很大,只需要满足顾客可以看到传播的内容打开橱窗不要有锋利边角,不要求一体,提高场景利用率 ...

  5. 【Javaweb】Servlet三|实现Servlet程序的几种方法及Servlet继承体系说明

    GET和POST请求的分发处理 代码如下 注意页面要和index位置一样否则浏览器找不到 注意大小写 HttpServletRequest httpServletRequest = (HttpServ ...

  6. JavaWeb项目练习(学生选课管理系统)二【新建数据库】

    思路 1.页面美化css这部分,挖个坑,我打算做好一点所以先空着.× 2.需要做四个数据表(学生.教师.管理员.课程) 关联: 学生有个人课表 教师有教授课程和个人课表 管理员有全部权限(关联所有数据 ...

  7. 【驱动】串口驱动分析(二)-tty core

    前言 tty这个名称源于电传打字节的简称,在linux表示各种终端,终端通常都跟硬件相对应.比如对应于输入设备键盘鼠标,输出设备显示器的控制终端和串口终端.也有对应于不存在设备的pty驱动.在如此众多 ...

  8. 掌握这些,轻松管理BusyBox:如何交叉编译和集成BusyBox

    在嵌入式系统中,由于设备的资源限制,需要开发人员寻找一种轻量.小型且使用广泛的工具集.而 BusyBox 就是这样一个在嵌入式系统中非常实用的工具集.本文将介绍如何在 Ubuntu 22.04 平台上 ...

  9. 将多个txt文件中的内容写在一个txt中的方法

    import os filename='./train_data/img_' for i in range(1,19736): newfile=filename+str(i)+'.txt' if os ...

  10. Scrapy-settings.py常规配置

    # Scrapy settings for scrapy_demo project # # For simplicity, this file contains only settings consi ...