再有人问你ThreadLocal,就把这篇文章扔给他

简介: 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

image.png

一、ThreadLocal是什么?

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

二、举个例子如下:

package cn.liuhaihua;
      
    public class SequenceNumber {  
           
        //①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  
        private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){  
            public Integer initialValue(){  
                return 0;  
            }  
        };  
           
        //②获取下一个序列值  
        public int getNextNum(){  
            seqNum.set(seqNum.get()+1);  
            return seqNum.get();  
        }  
          
        public static void main(String[ ] args)   
        {  
             SequenceNumber sn = new SequenceNumber();  
               
             //③ 3个线程共享sn,各自产生序列号  
             TestClient t1 = new TestClient(sn);    
             TestClient t2 = new TestClient(sn);  
             TestClient t3 = new TestClient(sn);  
             t1.start();  
             t2.start();  
             t3.start();  
        }

        private static class TestClient extends Thread  
        {  
            private SequenceNumber sn;  
            public TestClient(SequenceNumber sn) {  
                this.sn = sn;  
            }  
            public void run()  
            {  
                //④每个线程打出3个序列值  
                for (int i = 0; i < 3; i++) {  
                    System.out.println("thread["+Thread.currentThread().getName()+  
                            "] sn["+sn.getNextNum()+"]");  
                }
            }  
        }  
    }

运行结果

image.png

从现象看一下,为什么每个线程打印出来的数据都不受影响呢?下面我们一起来看下ThreadLocal源码,分析一下原因

三、ThreadLocal源码分析

image.png

以上源码是jdk1.8版本里面,带着上面的疑问,我们来看一下主要方法,

当我们的线程调用
`
sn.getNextNum()的时候,会执行
`

        public int getNextNum(){  
            seqNum.set(seqNum.get()+1);  
            return seqNum.get();  
        }

然后会先调用
ThreadLocal里面的get方法,方法大概内容,先去ThreadLocalMap看一遍是否存在?

存在的话,则取出值来,转化成T类型的返回

不存在的话,然后在调用setInitialValue,返回一个null

  /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }
 protected T initialValue() {
        return null;
  }
  /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }

setInitialValue先查询是否存在值,存在的话,直接覆盖,不存在的话,先创建map在设置 初始化值是一个null

   * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

下面我们再来看一下set(),存储数据的过程, 如我们例子所示,当我们调用

 seqNum.set(seqNum.get()+1);  会调用ThreadLocal.set(T value) 方法

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如果map存在值的话,会直接覆盖,不存在的话会创建一个map并且初始化值. 上述示例说明,每个线程都是单独维护一套自己的变量,从源码看得出来,其主要作用的是里面的内部类ThreadLocalMap,每个线程单独把自己的变量存在在不同的Entry,从而实现线程之间数据不相互依赖,

最后总结一下

ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。

来源:Java杂记


image.png

欢迎扫码加入阿里云开发者社区的【11大垂直技术领域开发者社群】

目录
相关文章
|
Java Linux
使用jps强制关闭java进程
使用jps强制关闭java进程
1532 0
使用jps强制关闭java进程
|
3月前
|
弹性计算 人工智能 运维
阿里云服务器购买:38元1年200M轻量和99元1年云服务器ECS如何选择?配置全解析
阿里云38元/年200M轻量服务器(新用户秒杀)与99元/年ECS经济型e实例如何选?本文对比核心配置:前者峰值带宽高、一键部署、运维零门槛,适合新手建站;后者固定带宽稳、弹性扩展强、可对接RDS等企业服务,适合长期稳定使用。按场景对号入座,不花冤枉钱。(239字)
439 1
|
3月前
|
人工智能 关系型数据库 MySQL
告别“鱼的记忆”:PolarDB Mem0 赋予 AI Agent “长期记忆”
阿里云 PolarDB MySQL 版推出 Mem0 托管服务,专为 AI Agent 打造长期记忆能力。融合向量库与图引擎,100%兼容开源 Mem0,支持语义提取、多模态检索与记忆演进,存储成本降30%+,助力 Agent 实现“千人千面”的持续学习与智能进化。
|
5月前
|
存储 缓存 NoSQL
阿里云数据库KVStore兼容Redis亚毫秒级的稳定时延,Tair数据库企业版
阿里云KVStore(兼容Redis®)提供亚毫秒级时延、99.99%高可用,支持多存储介质降本30%~70%,兼具高性能、高可靠与易运维,助力米哈游、雪球等企业实现架构升级,Tair企业版更以丰富特性重新定义云原生键值数据库。
307 2
|
存储 数据采集 Java
InfluxDB 的学习笔记
在Java项目中实现InfluxDB的落地应用,主要包括添加InfluxDB的Java客户端依赖、创建数据库连接、执行数据的增删改查操作等步骤
1140 2
|
消息中间件 Java 数据管理
RocketMQ原理—2.源码设计简单分析上
本文介绍了NameServer的启动脚本、启动时会解析哪些配置、如何初始化Netty网络服务器、如何启动Netty网络服务器,介绍了Broker启动时是如何初始化配置的、BrokerController的创建以及包含的组件、BrokerController的初始化、启动、Broker如何把自己注册到NameServer上、BrokerOuterAPI是如何发送注册请求的,介绍了NameServer如何处理Broker的注册请求、Broker如何发送定时心跳
|
Kubernetes Cloud Native 调度
《分布式任务调度框架深度对比:Quartz/XXL-JOB/Elastic-Job/PowerJob选型指南》​
根据IDC预测,到2025年全球将有75%的企业任务调度系统需要重构以适应云原生架构。技术雷达监测:定期关注CNCF技术趋势报告渐进式改造:从非核心业务开始验证新框架人才储备:重点培养具备K8s Operator开发能力的调度专家评估现有系统的云原生适配度在测试环境部署PowerJob 4.3.3参与CNCF调度技术社区讨论制定6个月框架迁移路线图(注:本文数据来自各框架官方路线图、CNCF年度报告及笔者压力测试结果,转载请保留出处)
2836 0
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
805 16
|
存储 Kubernetes API
k8s学习-ConfigMap(创建、使用、更新、删除等)
k8s学习-ConfigMap(创建、使用、更新、删除等)
4117 0
|
人工智能 安全 算法
基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。
本文深入探讨了基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。C语言因其高效、可移植、灵活及成熟度高等特点,在嵌入式系统开发中占据重要地位。文章还介绍了从系统需求分析到部署维护的完整开发流程,以及中断处理、内存管理等关键技术,并展望了嵌入式系统在物联网和人工智能领域的未来发展。
846 1