通过查看ThreadLocal的源码进行简单理解

news2024/12/22 20:27:54

目录

为什么要使用ThreadLocal?

简单案例

ThreadLocal源码分析 

断点跟踪


为什么要使用ThreadLocal

在多线程下,如果同时修改公共变量可能会存在线程安全问题,JDK虽然提供了同步锁与Lock等方法给公共访问资源加锁,但在高并发的场景下,如果多个线程争抢一把锁会出现大量的锁等待时间,让系统的响应时间变慢。因此JDK又提供了新的思路,用空间换取时间也就是使用ThreadLocal。

简单案例

先进行一个简单案例

@SpringBootTest
public class TestThreadLocal {
    private ThreadLocal<String> local = new ThreadLocal<>();

    @Test
    public void test01() throws Exception {
        new Thread(()->{
            local.set("local_A");
            System.out.println(Thread.currentThread().getName());
            System.out.println(local.get());
        },"a").start();

        new Thread(()->{
            local.set("local_B");
            System.out.println(Thread.currentThread().getName());
            System.out.println(local.get());
        },"b").start();
    }
}

运行结果为如下

a
local_A
b
local_B

待会我们断点查看上述代码的运行过程,在此之前我们先来查看一下ThreadLocal源码。

ThreadLocal源码分析 

首先给出ThreadLocal类中主要的代码如下

public class ThreadLocal<T> {
     public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //根据threadLocal对象从map中获取Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取保存的数据
                T result = (T)e.value;
                return result;
            }
        }
        //如果没有进行set方法,那么执行该方法
        //初始化数据
        return setInitialValue();
    }
    
    private T setInitialValue() {
        //获取要初始化的数据,该方法需要自己重写实现
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果map不为空
        if (map != null)
            //将初始值设置到map中,key是this,即threadLocal对象,value是初始值
            map.set(this, value);
        else
           //如果map为空,则需要创建新的map对象
            createMap(t, value);
        return value;
    }
    
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果map不为空
        if (map != null)
            //将值设置到map中,key是this,即threadLocal对象,value是传入的value值
            map.set(this, value);
        else
           //如果map为空,则需要创建新的map对象
            createMap(t, value);
    }
    
     static class ThreadLocalMap {
        ...
     }
     ...
}

上面三个主要方法主要操作的是ThreadLocalMap对象,而ThreadLocalMap又是ThreadLocal的静态内部类。

主要的ThreadLocalMap的源码如下

static class ThreadLocalMap {
    //维护了一个静态内部类,用来存储主要的数据
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
   }
   //存在一个Entry数组,该table对象中主要存放多个ThreadLocal与value的值
   private Entry[] table;
   ...
}

ThreadLocalMap中又维护了一个内部类Entry,同时Entry类又继承了WeakReference(弱引用与ThreadLocal对象)。

在Thread类中维护了一个ThreadLocalMap类型的变量threadLocals。

因此ThreadLocal的整体结构大致如下

每一个set()的数据都会被保存在Entry数组中。源码查看完毕,接下来我们断点跟踪上述案例。

断点跟踪

对set()方法中进行断点查看,当线程a第一次执行set()时,先获取ThreadLocalMap对象是否为空,如果为空,那么执行createMap()方法,将当前线程与需要存储的值作为参数传递

在createMap()中,只做了一件事,那就是创建一个对象并放入当前线程。

set()方法查看完毕,接下来查看get()方法做了什么事情

 在get方法中,获取当前线程以及线程中的map对象,从对象中根据key获取存储的值并返回。如果没有执行过set方法,会执行setInitalValue()方法。

而在该方法中其实也只做了一件事,那就是初始化值。

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

initialValue()默认直接返回null,需要我们自己重写实现,具体代码如下

@SpringBootTest
public class TestThreadLocal {
    private ThreadLocal<User> local = new ThreadLocal<User>(){
        @Override
        protected User initialValue() {
            User user = new User("张三", 18);
            return user;
        }
    };

    @Test
    public void test03() throws Exception {
        new Thread(()->{
            User user = local.get();
            System.out.println(user.toString());
        }).start();
    }
}

运行结果如下

User(name=张三, age=18)

 以上就是我对ThreadLocal的简单理解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1277928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCV-Python:计算机视觉框架

1.背景 俗话说“工欲善其事必先利其器”&#xff0c;想要学好计算机视觉&#xff0c;需要借助于相关的计算机视觉库&#xff0c;这样在进行学习的时候可以达到事半功倍的效果。 2.早期计算机视觉框架概述 Matlab的最早历史可以追溯到1970年&#xff0c;开始是作为数据处理工…

YUVRGB

一、直观感受 根据上面的图片&#xff0c;不难看出&#xff1a; RGB的每个分量&#xff0c;是对当前颜色的一个亮度值Y分量对呈现出清晰的图像有着很大的贡献Cb、Cr分量的内容不太容易识别清楚YUV将亮度信息&#xff08;Y&#xff09;与色度信息&#xff08;UV&#xff09;分离…

最多不一定最好,只有适合的才是最好的!电脑的内存多大才是合理的

RAM&#xff0c;或称随机存取存储器&#xff0c;是最好的笔记本电脑和最好的电脑最重要的组成部分之一。硬盘驱动器&#xff08;HDD&#xff09;或固态驱动器&#xff08;SSD&#xff09;存储可以被视为电脑的长期内存&#xff0c;内存是其短期内存。内存可以跟踪后台运行的应用…

P6 链表 插入数据节点 尾插法指定节点插入

目录 前言 ​编辑 01 链表指定节点后插入数据&#xff08;根据节点号插入&#xff09; 测试代码 02 链表指定节点后插入数据&#xff08;根据节点的数据插入&#xff09; 尾插法的代码 前言 …

for循环定义域问题

记录一个偶然发现的问题&#xff0c;代码如下 int main(int argc, char *argv[], char *envp[]){for(int i 1; i < 10; i);printf("%d",i); return 0; } 可以看到for循环后加了一个分号&#xff0c;按理说应该报变量i未定义的错误&#xff0c;但此时在编译器中…

RCE绕过

1.[SCTF 2021]rceme 总结下获取disabled_funciton的方式 1.phpinfo() 2.var_dump(ini_get(“disable_functions”)); 3.var_dump(get_cfg_var(“disable_functions”)); 其他的 var_dump(get_cfg_var(“open_basedir”)); var_dump(ini_get_all()); <?php if(isset($_POS…

使用coco数据集进行语义分割(1):数据预处理,制作ground truth

如何coco数据集进行目标检测的介绍已经有很多了&#xff0c;但是关于语义分割几乎没有。本文旨在说明如何处理 stuff_train2017.json stuff_val2017.json panoptic_train2017.json panoptic_val2017.json&#xff0c;将上面那些json中的dict转化为图片的label mask&am…

C++入门篇第十篇----继承

前言&#xff1a; 本篇我们将开始讲解C的继承&#xff0c;我想要说的是&#xff0c;C的主体基本就是围绕类和对象展开的&#xff0c;继承也是以类和对象为主体&#xff0c;可以说&#xff0c;C相较于C优化的地方就在于它对于结构体的使用方法的高度扩展和适用于更多实际的场景…

ffmpeg 任意文件读取漏洞/SSRF漏洞 (CVE-2016-1897/CVE-2016-1898)

漏洞描述 影响范围 FFmpeg 2.8.x < 2.8.5FFmpeg 2.7.x < 2.7.5FFmpeg 2.6.x < 2.6.7FFmpeg 2.5.x < 2.5.10 漏洞环境及利用 搭建docker环境 访问8080端口看到上传界面 由于vulhub并没有讲述该漏洞如何复现&#xff0c;我们需要进入环境查看源码 <?php if(!…

电大搜题:开启你的学习新篇章

广西开放大学&#xff0c;作为一所具有悠久历史和丰富经验的广播电视大学&#xff0c;在教育领域享有盛誉。如今&#xff0c;随着科技的迅猛发展&#xff0c;广西开放大学推出了电大搜题微信公众号&#xff0c;为广大学子提供了一个便捷、高效的学习工具。 电大搜题微信公众号…

2_企业级Nginx使用-day1

#企业级Nginx使用-day1 学习目标和内容 1、能够了解Nginx的信号参数 2、能够进行平滑升级Nginx 3、能够配置server虚拟机 4、能够部署上线项目到LNMP架构中 5、能够了解Nginx的常用官方模块 6、能够了解日志相关使用 一、重装和升级 在实际业务场景中&#xff0c;需要使用软件…

linux之buildroot(2)配置toolchain

Linux之buildroot(2)配置toolchain Author&#xff1a;Onceday Date&#xff1a;2023年11月27日 漫漫长路&#xff0c;才刚刚开始… 全系列文章请查看专栏: buildroot编译框架_Once_day的博客-CSDN博客 参考文档&#xff1a; Buildroot - Making Embedded Linux Easy 文章…

.net7.0中把exe和dll分开打包

之前写过 C#把dll分别放在指定的文件夹_wpf core dll 放文件夹-CSDN博客 C#把dll打包到exe_c# 打包exe_故里2130的博客-CSDN博客 这都是老技术了&#xff0c;可以进行参考。 现在的.netcore系列有单独支持把exe和dll分开打包的功能了&#xff0c;当然也支持.net7.0和.net8.…

吉他初学者学习网站搭建系列(4)——如何查询和弦图

文章目录 背景实现ChordDbvexchords 背景 作为吉他初学者&#xff0c;如何根据和弦名快速查到和弦图是一个必不可少的功能。以往也许你会去翻和弦的书籍查询&#xff0c;像查新华字典那样&#xff0c;但是有了互联网后我们不必那样&#xff0c;只需要在网页上输入和弦名&#…

在国外,职业生命期长,是因为敬业吗?

不知道有没有朋友关注围棋比赛&#xff1f;昨天进行了农心杯三国围棋擂台赛第二轮的攻擂战&#xff0c;结果中国棋手谢尔豪半目险胜韩国棋手元晟臻。 棋局本身很精彩&#xff0c;更引人注目的是韩国棋手已经是 38 岁高龄。所以有网友发出感叹&#xff0c;中国的领军人物玩直播、…

Xilinx FPGA平台DDR3设计详解(三):DDR3 介绍

本文介绍一下常用的存储芯片DDR3&#xff0c;包括DDR3的芯片型号识别、DDR3芯片命名、DDR3的基本结构等知识&#xff0c;为后续掌握FPGA DDR3的读写控制打下坚实基础。 一、DDR3芯片型​号 电路板上的镁光DDR3芯片上没有具体的型号名。 ​如果想知道具体的DDR3芯片型号&#…

Python中用于机器学习的Lazy Predict库

Python是一种多功能语言&#xff0c;你可以用它来做任何事情。Python的一个伟大之处在于&#xff0c;有这么多的库使它变得更加强大。Lazy Predict就是其中一个库。它是机器学习和数据科学的一个很好的工具。在本文中&#xff0c;我们将了解它是什么&#xff0c;它做什么&#…

k8s中Service负载均衡和Service类型介绍

目录 一.service介绍 二.service参数详解 三.定义service的两种方式 1.命令行expose 2.yaml文件 四.service负载均衡配置 1.kube-proxy代理模式 &#xff08;1&#xff09;设置ipvs &#xff08;2&#xff09;负载均衡调度策略 2.会话保持 3.案例演示 五.四种Servi…

51单片机 -全球【最笨】学习法

全球属我最笨 1> 还有比我笨的吗&#xff1f;2> 4个回合&#xff01;拿下51单片机第1回合> 先学各个外围驱动模块第2回合> 自己写各个外围模块程序第3回合> 学习开源实战小项目第4回合> 小项目-视频讲解 1> 还有比我笨的吗&#xff1f; 有兄弟&#xff0…

(C语言)逆序输出字符串

#include<stdio.h> #include<string.h> int main() {int i;char s[100];scanf("%s",&s);int count strlen(s);for(int i count -1;i > 0; i --)printf("%c",s[i]);return 0;} 代码运行截图&#xff1a; 注&#xff1a;侵权可删