ThreadLocal 基础入门学习(代码案例详解)

简介: 📝本篇用代码案例详解ThreadLocal,具体包含基础入门,案例学习,应用对比!欢迎沟通学习打卡交流!
【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行!

博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端、后台、数据库、项目案例等相关知识点总结,感谢你的阅读和关注,希望我的博客能帮助到更多的人,分享获取新知,大家一起进步!

吾等采石之人,应怀大教堂之心,愿我们奔赴在自己的热爱里…

@[toc]


一、基础入门

☕️业务场景:多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。


📝ThreadLocal的使用

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

在这里插入图片描述

📣ThreadLocal原理:在ThreadLocal类中有一个Map集合,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本

🎩经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection;


二、案例应用

☕️ThreadLocal 结合线程池使用案例分享

/**
 * @program: demo
 * @description: ThreadLocal 结合线程池使用案例分享
 * @author: 辰兮要努力
 * @create: 2021-11-15 21:48
 */
public class ThreadLocalDemo {

    /**
     *
     * ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
     * 变量:variable
     */
    private static  ThreadLocal<String> threadLocalVar = new ThreadLocal<String>();

    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + threadLocalVar.get());
        //清除本地内存中的本地变量
        threadLocalVar.remove();
    }

    public static void main(String[] args) throws InterruptedException {

        ExecutorConfig.getThreadPoolInstance().execute(new Runnable() {
            @Override
            public void run() {

                ThreadLocalDemo.threadLocalVar.set("辰兮");
                print("2021");
                //打印本地变量
                System.out.println("执行移除方法后 : " + threadLocalVar.get());
            }
        });

        ExecutorConfig.getThreadPoolInstance().execute(new Runnable() {
            @Override
            public void run() {
                ThreadLocalDemo.threadLocalVar.set("辰兮要努力");
                print("2022");
                System.out.println("执行移除方法后 : " + threadLocalVar.get());
            }
        });

    }
}

控制台输出

2022 :辰兮要努力
执行移除方法后 : null
2021 :辰兮
执行移除方法后 : null

线程池帮助类

import org.apache.log4j.Logger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;


/**
 * @program: ExecutorConfig
 * @description: 线程池相关帮助类
 * @author: 辰兮要努力
 */
public class ExecutorConfig {

    /**
     * 记录对应的日志
     */
    private static Logger logger = Logger.getLogger(ExecutorConfig.class);

    private static ThreadPoolTaskExecutor thPoolInstance = null;

    /**
     * 线程池
     *
     * @return ThreadPoolTaskExecutor
     * @author 辰兮要努力
     */
    public static ThreadPoolTaskExecutor getThreadPoolInstance() {
        if (thPoolInstance != null) {
            return thPoolInstance;
        }

        synchronized (ExecutorConfig.class) {
            if (thPoolInstance == null) {
                try {
                    // 获取统一线程池
                    thPoolInstance = ApplicationContextHolder.getBean(ThreadPoolTaskExecutor.class);

                    if (thPoolInstance == null) {
                        // 如果统一线程池还是为空,将启动本地创建线程,进行保护。
                        thPoolInstance = getThPoolInstance();
                    }
                } catch (Exception e) {
                    logger.error("getThreadPoolInstance -> create thread pool error", e);
                } finally {
                    // 如果统一线程池还是为空,将启动本地创建线程,进行保护。
                    if (thPoolInstance == null) {
                        thPoolInstance = getThPoolInstance();
                    }
                }
            }
        }

        return thPoolInstance;
    }

    /**
     * 获取本地线程池
     *
     * @return ThreadPoolTaskExecutor
     * @author 辰兮要努力
     * @date 2021-9-25 10:58:53
     */
    private static ThreadPoolTaskExecutor getThPoolInstance() {
        /**
         * 如果对应的线程池不为空则直接返回,如果为空
         */
        if (thPoolInstance != null) {
            return thPoolInstance;
        }

        try {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 核心线程数10:线程池创建时候初始化的线程数
            executor.setCorePoolSize(10);
            // 最大线程数15:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
            executor.setMaxPoolSize(15);
            // 缓冲队列25:用来缓冲执行任务的队列
            executor.setQueueCapacity(25);
            // 允许线程的空闲时间200秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
            executor.setKeepAliveSeconds(200);
            // 线程池名的前缀:设置好了之后可以方便定位处理任务所在的线程池
            executor.setThreadNamePrefix("chenXI-");
            /**
             * 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略, 当线程池没有处理能力的时候,
             * 该策略会直接在 execute 方法的调用线程中运行被拒绝的任务; 如果执行程序已关闭,则会丢弃该任务
             */
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
            executor.setWaitForTasksToCompleteOnShutdown(true);
            // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
            executor.setAwaitTerminationSeconds(60 * 2);
            executor.initialize();
            thPoolInstance = executor;
        } catch (Exception e) {
            logger.error("getThPoolInstance-> create thread pool error", e);
        }

        return thPoolInstance;
    }
}

帮助类:在spring环境中获取非spring容器管理的bean

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @program: ApplicationContextHolder
 * @description: 在spring环境中获取非spring容器管理的bean
 * @author: 辰兮要努力
 * @create: 2021-9-25 13:30:02
 */
public class ApplicationContextHolder implements ApplicationContextAware {


    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        applicationContext = ctx;
    }


    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }


    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }


    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

}

首先我们要清楚一个概念 成员变量局部变量

成员变量: 直接在类中声明的变量叫成员变量(又称全局变量)

局部变量:方法中的参数、方法中定义的变量和代码块中定义的变量统称为局部变量

在这里插入图片描述


三、源码学习

📖Java官方对ThreadLocal类的定义如下:

  • ThreadLocal类用于提供线程内部的局部变量。
  • 这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。
  • ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。

☕️ThreadLocal 底层源码学习

public T get() 返回当前线程所对应的线程局部变量

//ThreadLocal中的get方法
    public T get() {
        //获取到当前的线程对象
        Thread t = Thread.currentThread();
        //进而获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果此map存在
        if (map != null) {
            //以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            //找到对应的存储实体 e
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果map不存在,调用setInitialValue进行初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        //调用initialValue获取初始化的值
        T value = initialValue();
        //获取当前线程对象
        Thread t = Thread.currentThread();
        //获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果此map存在
        if (map != null)
            //存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            调用createMap进行ThreadLocalMap对象的初始化,并将此实体作为第一个值
            createMap(t, value);
        return value;
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

public void set(T value)

    public void set(T value) {
        //获取到当前的线程对象
        Thread t = Thread.currentThread();
        //进而获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果ThreadLocalMap存在,则以当前的ThreadLocal为key,value作为值设置entry
        if (map != null)
            map.set(this, value);
        else
            //调用createMap进行ThreadLocalMap对象的初始化,并将此实体作为第一个值
            createMap(t, value);
    }
    //创建一个与线程t关联的ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用

      public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             //以当前ThreadLocal为key删除对应的实体entry
             m.remove(this);
     }

移除当前线程存储的 Value 值。当 ThreadLocal 不在使用,最好在 finally 语句块中,调用 remove() 方法,释放去 Value 的引用,避免内存泄露。


在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

📝ThreadLocal和synchronized对比

使用ThreadLocal代替synchronized来保证线程安全。同步机制采用了以时间换空间的方式,而ThreadLocal采用了以空间换时间的方式。

前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

📖☕️🌊📝📚🎩🚀📣

后续会继续总结ThreadLocal的应用案例,未来见……


📝非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!

🚀 愿我们奔赴在自己的热爱里!

目录
相关文章
|
数据可视化 大数据 BI
数据可视化大屏的设计规范和案例参考(使用AxureRP软件设计)
在信息化浪潮中,数据可视化不再仅限于单纯的数据呈现,而是逐渐演变为一种能够直观揭示复杂数据内在关联、趋势变化以及关键洞察的艺术形式。
1149 3
|
SQL Prometheus 监控
TiDB集群监控与性能分析
【2月更文挑战第28天】本章将深入探讨TiDB集群的监控与性能分析技术。我们将介绍TiDB集群监控的关键指标、监控工具的使用,以及如何进行性能分析和调优。通过本章节的学习,读者将能够掌握TiDB集群的监控与性能分析方法,提高数据库的运行效率和稳定性。
|
安全 JavaScript Go
Vue中的v-html指令有什么潜在的安全风险?如何防范?
Vue中的v-html指令有什么潜在的安全风险?如何防范?
1255 1
|
11月前
|
SQL 运维 网络安全
【实践】基于Hologres+Flink搭建GitHub实时数据查询
本文介绍了如何利用Flink和Hologres构建GitHub公开事件数据的实时数仓,并对接BI工具实现数据实时分析。流程包括创建VPC、Hologres、OSS、Flink实例,配置Hologres内部表,通过Flink实时写入数据至Hologres,查询实时数据,以及清理资源等步骤。
|
存储 SQL 关系型数据库
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
MySQL如何进行分库分表、数据迁移?从相关概念、使用场景、拆分方式、分表字段选择、数据一致性校验等角度阐述MySQL数据库的分库分表方案。
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
|
11月前
|
敏捷开发 监控 测试技术
探索自动化测试框架的构建与优化####
在软件开发周期中,自动化测试扮演着至关重要的角色。本文旨在深入探讨如何构建高效的自动化测试框架,并分享一系列实用策略以提升测试效率和质量。我们将从框架选型、结构设计、工具集成、持续集成/持续部署(CI/CD)、以及最佳实践等多个维度进行阐述,为软件测试人员提供一套系统化的实施指南。 ####
Java系列之 查看某一部分代码执行时间长短
这篇文章介绍了如何在Java中测量某段代码或方法的执行时间,通过记录执行前后的系统当前时间毫秒数,然后计算两者的差值得到执行时间,示例中展示了如何使用`System.currentTimeMillis()`来测量一个循环方法的执行时长。
Java系列之 查看某一部分代码执行时间长短
|
关系型数据库 MySQL 索引
WHERE Clause Optimization
本节探讨了WHERE子句的优化方法,虽然示例基于SELECT语句,但也适用于DELETE和UPDATE语句。MySQL自动执行多种优化,例如仅计算一次索引使用的常量表达式、快速检测无效表达式、合并HAVING和WHERE子句、优先读取常量表、寻找最佳连接组合、使用内存中的临时表、选择最佳索引以及在某些情况下仅使用索引树解析查询,从而提升查询效率。
|
存储 监控 关系型数据库
如何升级MySQL版本?
如何升级MySQL版本?
781 2
|
机器学习/深度学习 人工智能 自然语言处理