java 浅谈ThreadLocal底层源码(通俗易懂)

简介: java ThreadLocal介绍及源码分析 通俗易懂!

目录

一、ThreadLocal类基本介绍

       1.概述 :

       2.作用及特定 :

二、ThreadLocal类源码解读

       1.代码准备 :

           1.1 图示

           1.2 数据对象

           1.3 测试类

           1.4 运行测试

       2.源码分析 :

           2.1 set方法解读

           2.2 get方法解读


一、ThreadLocal类基本介绍

       1.概述 :

       (1) ThreadLocal,本地线程变量,是Java中的一个类。ThreadLocal类提供了一种线程绑定机制,可以将状态与线程(Thread)关联起来。ThreadLocal类如下图所示 :

image.gif 编辑

       (2)每个线程都会有自己独立的一个ThreadLocal变量,该变量对其他线程而言是封闭且隔离的,因此对该变量的读写操作只会影响到当前执行线程的这个变量,而不会影响到其他线程的同名变量

       2.作用及特定 :

       (1) ThreadLocal可以实现在同一个线程数据共享,从而解决多线程数据安全问题。

       (2) 通过ThreadLocal类实例的set方法,可以为当前线程关联一个数据(变量,对象,数组)

       (3) 通过ThreadLocal类实例的get方法,可以像Map一样存取key-value键值对(其中key为当前线程),注意,显式的用法上与Map不相同

       (4) 每一个ThreadLocal对象,只能为当前线程关联一个数据,若想为当前线程关联多个数据,就需要使用到多个ThreadLocal实例

       (5) ThreadLocal实例往往定义为static类型。

       (6) ThreadLocal中保存的数据,会在线程销毁后自动释放


二、ThreadLocal类源码解读

       1.代码准备 :

           1.1 图示

               首先,我们要把代码打通,确保ThreadLocal对象可以在同一线程中实现数据共享。根据下图来定义所需要的测试类 :

image.gif编辑

               在T1类,T1Service类,以及T2DAO类中分别打印出当前线程的名字,以及放入到threadLocal1对象中的数据对象,对比三个类打印出的线程名字和数据对象是否相同,即可验证“ThreadLocal可以实现在同一个线程数据共享”。

           1.2 数据对象

               定义Apple类和Grape类用作测试的数据对象。

               Apple类代码如下 :

package threadlocal;
public class Apple {
}

image.gif

               Grape类代码如下 :

package threadlocal;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Grape {
}

image.gif

           1.3 测试类

              T1类代码如下 :

package threadlocal;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class T1 {
    //定义一个静态的ThreadLocal对象
    public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
    public static void main(String[] args) {
        //在主线程中启动一个新的子线程
        new Thread(new Task()).start();
    }
    public static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println("(Task)run方法,当前线程名 = " + Thread.currentThread().getName());
            Apple apple = new Apple();
            Grape grape = new Grape();
            //向threadLocal1对象中放入一个Apple对象
            System.out.println("(Task)run方法,放入的对象 = " + apple);
            threadLocal1.set(apple);
            new T1Service().test();
        }
    }
}

image.gif

               T1Service类代码如下 :

package threadlocal;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class T1Service {
    public void test() {
        String name = Thread.currentThread().getName();
        System.out.println("(T1Service)当前线程名 =  " + name);
        Object o = T1.threadLocal1.get();
        System.out.println("(T1Service)得到的对象o = " + o);
        new T2DAO().test();
    }
}

image.gif

               T2DAO类代码如下 :

package threadlocal;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class T2DAO {
    public void test() {
        String name = Thread.currentThread().getName();
        System.out.println("(T2DAO)当前线程名 = " + name);
        Object o = T1.threadLocal1.get();
        System.out.println("(T2DAO)得到的对象o = " + o);
    }
}

image.gif

           1.4 运行测试

               运行结果 :

image.gif编辑

       2.源码分析 :

           2.1 set方法解读

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

image.gif

               第一步,可以看到,set方法中首先就获取到了当前线程,而当前线程,就是调用set方法时——线程类run方法所在的那个线程;说明set方法和当前线程是关联的

               第二步通过当前线程对象获取到了ThreadLocalMap对象。此处的ThreadLocalMap,是ThreadLocal类的一个静态内部类。如下图所示 :

image.gif编辑

               注意,为什么是通过当前线程对象来获取ThreadLocalMap对象呢?

               因为当前线程持有自己的ThreadLocal对象(该对象调用了set方法),而ThreadLocalMap又是ThreadLocal的一个内部类.
               
继续,接着判断得到的map对象是否为空,如下图所示 :

image.gif编辑

               如果不为空,就将当前的ThreadLocal对象(this,即指在T1类中一开始调用set方法的ThreadLocal对象)和该对象调用set方法时放入的数据(value,此处是放入的Apple对象)。从这里也可以看出,如果同一个ThreadLocal对象再次调用set方法,会对存入的数据(value)进行替换——即"每一个ThreadLocal对象,只能为当前线程关联一个数据"。

               如果为空,就创建一个与当前线程对象关联的ThreadLocalMap对象,并将目标数据放入(value)。

               在set方法调用处设一个断点,进入Debug界面后可以看到当前线程对象名字,如下图所示 :

image.gif编辑

               在this对象中向下找,可以找到一个threadLocals属性,发现它就是ThreadLocalMap类型,如下图所示 :

image.gif编辑

               我们也可以Thread类的源码中找到这个属性,如下图所示 :

image.gif编辑

               而该属性下的table, 就是实际存放数据的地方(可以看到table是ThreadLocalMap的内部类Entry类型的数组)。当set方法执行完毕后,我们可以看到table数组中的Apple对象,如下图所示 :

image.gif编辑

               实际上,table就是线程用于管理ThreadLocal实例的容器

               而table数组中每个元素的referent属性(弱引用对象),也就是ThreadLocal对象,此处可以看到与之前一致,如下 :

image.gif编辑

           2.2 get方法解读

               get方法源码如下 : (PS : 注意此处是泛型在方法上的应用,而不是自定义泛型方法)

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

image.gif

               第一步,和set方法一样,都是先得到当前的线程对象。为啥?因为只有得到了当前线程对象,才能找到它的属性threadLocals[ThreadLocal$ThreadLocalMap类型],继而找到该属性维护的table数组(ThreadLocal$ThreadLocalMap$Entry[]类型),然后根据当前线程持有的的ThreadLocal实例,找到数组中对应的Entry元素,继而拿到它的属性value(保存的数据)

               显然,get方法的源码中也的确是这么干的。通过当前线程对象拿到ThreadLocalMap对象,我们可以看一下getMap(t)的源码,如下图所示 :

image.gif编辑

               之后,判断map对象是否为空,如果不为空,就根据当前持有的ThreadLocal实例去找table数组中对应的Entry元素。继续往下走 :

image.gif编辑

               拿到对应的Entry元素后,还要进行判断,如果该元素的确是存在的(表明当前的ThreadLocal实例已经为当前线程绑定过数据[一个value]), 就取出该Entry元素的value属性,此处为Object类型的apple对象,然后返回。

               🆗,以上就是对ThreadLocal的一些浅显解读。感谢阅读!

目录
相关文章
|
5月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
398 7
|
5月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
5月前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
185 5
|
4月前
|
JavaScript Java 关系型数据库
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
146 6
家政系统源码,java版本
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
187 3
|
4月前
|
供应链 JavaScript 前端开发
Java基于SaaS模式多租户ERP系统源码
ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业的运营效率和管理水平,它是一种先进的企业管理理念和信息化管理系统。 适用于小微企业的 SaaS模式多租户ERP管理系统, 采用最新的技术栈开发, 让企业简单上云。专注于小微企业的应用需求,如企业基本的进销存、询价,报价, 采购、销售、MRP生产制造、品质管理、仓库库存管理、财务应收付款, OA办公单据、CRM等。
265 23
|
5月前
|
Java
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
130 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
|
4月前
|
存储 安全 Java
深入探究Java中ThreadLocal的工作原理和用途
总结起来,ThreadLocal是Java多线程编程中一个非常有用的工具,通过为每个线程分配独立的变量副本,实现线程隔离,避免资
99 9
|
5月前
|
Java
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
前言 有了前文对简单实用的学习 【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门 聪明的你,一定会想知道更多。哈哈哈哈哈,下面主播就...
98 6
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
|
5月前
|
安全 Java
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
前言 通过之前的学习是不是学的不过瘾,没关系,马上和主播来挑战源码的阅读 【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门 还有一件事
102 5
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue

热门文章

最新文章