热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

JavaThreadLocal的使用详解

ThreadLocal是线程私有的局部变量存储容器,可以理解成每个线程都有自己专属的存储容器,用来存储线程私有变量。ThreadLocal在日常开发框架中应用广泛,但用不好也会出现各种问题,本文就此讲解一下。

ThreadLocal是线程私有的局部变量存储容器,可以理解成每个线程都有自己专属的存储容器,用来存储线程私有变量。ThreadLocal 在日常开发框架中应用广泛,但用不好也会出现各种问题,本文就此讲解一下。

1. 应用场景

ThreadLocal 的常见应用场景有两种:

  1. 多线程并发场景中,用来保障线程安全。
  2. 处理较为复杂的业务时,使用ThreadLocal代替参数的显示传递。

1.1. 保障线程安全

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

ThreadLocal是除了加锁这种同步方式之外的一种,规避多线程访问出现线程不安全的方法。当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量,这样就不会存在线程不安全问题。

ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。

1.2. 显示传递参数

这里举几个例子:

示例1:获取接口的当前请求用户
在后台接口业务逻辑的全过程中,如果需要在多个地方获取当前请求用户的信息。通常的一种做法就是:在接口请求时,通过过滤器、拦截器、AOP等方式,从session或token中获取当前用户信息,存入ThreadLocal中。

在整个接口处理过程中,如果没有另外创建线程,都可以直接从ThreadLocal变量中获取当前用户,而无需再从Session、token中验证和获取用户。这种方案设计不仅提高性能,最重要的是将原本复杂的逻辑和代码实现,变得简洁明了。例如下面的这个例子:

(1)定义ThreadLocal变量:UserProfileThread.java

public class UserProfileThread {
    private static ThreadLocal USER_PROFILE_TL =new ThreadLocal<>();

    public static void  setUserProfile(UserProfile userProfile){
        USER_PROFILE_TL.set(userProfile);
    }

    public static UserProfile getUserProfile() {
        return USER_PROFILE_TL.get();
    }

    public static String getCurrentUser() {
        return Optional.ofNullable(USER_PROFILE_TL.get())
                .map(UserProfile::getUid)
                .orElse(UserProfile.ANONYMOUS_USER);
    }
}

(2)在过滤器中设置变量值:

   @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        UserProfile userProfile = null;
        // ... 验证和获取用户信息 userProfile
        UserProfileThread.setUserProfile(userProfile);
        filterChain.doFilter(servletRequest, servletResponse);
    }

(3)获取当前用户信息

//获取当前用户
String uid=UserProfileThread.getCurrentUser();
//获取当前用户对象
UserProfile user=UserProfileThread.getUserProfile();

示例2:spring框架中保证数据库事务在同一个连接下执行

要想实现jdbc事务, 就必须是在同一个连接对象中操作,多个连接下事务就会不可控,需要借助分布式事务完成。那spring框架如何保证数据库事务在同一个连接下执行的呢?

DataSourceTransactionManager 是spring的数据源事务管理器,它会在你调用getConnection()的时候从数据库连接池中获取一个connection, 然后将其与ThreadLocal绑定,事务完成后解除绑定。这样就保证了事务在同一连接下完成。

2. 实现原理

ThreadLocal类提供set/get方法存储和获取value值,但实际上ThreadLocal类并不存储value值,真正存储是靠ThreadLocalMap这个类。

每个线程实例都对应一个TheadLocalMap实例,我们可以在同一个线程里实例化很多个ThreadLocal来存储很多种类型的值,这些ThreadLocal实例分别作为key,对应各自的value,最终存储在Entry table数组中。
我们看看ThreadLocal的set方法:

public class ThreadLocal {
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    // 省略其他方法
}

set的逻辑比较简单,就是获取当前线程的ThreadLocalMap,然后往map里添加KV,K是当前ThreadLocal实例,V是我们传入的value。这里需要注意一下,map的获取是需要从Thread类对象里面取,看一下Thread类的定义。

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
    //省略其他
}

Thread类维护了一个ThreadLocalMap的变量引用。

因此,我们可以得出如下结论:

  1. 每个线程是一个Thread实例,其内部维护一个threadLocals的实例成员,其类型是ThreadLocal.ThreadLocalMap。
  2. ThreadLocal本身并不是一个容器,我们存取的value实际上存储在ThreadLocalMap中,ThreadLocal只是作为TheadLocalMap的key。

3. 注意事项

ThreadLocal实例有提供remove()方法,用于回收对象,清除对应的内存占用。这个方法通常容易被忽略,而导致出现了各种问题。如下面几种:

  • 线程复用:在“获取接口的当前请求用户”的例子中,Tomcat中是通过线程池来处理用户请求的,而线程池中线程是复用的。肯定会出现一个线程前后被不同用户的接口请求复用的情况,因此需要对用过的ThreaLocal变量进行覆盖或清除。
  • 内存溢出:由于ThreadLocalMap的生命周期跟Thread一样长,如果创建的ThreadLocal变量很多,即对应的key占用的内存很大,但却没有手动删除,到了一定程度就会导致内存泄漏。

以上就是Java ThreadLocal的使用详解的详细内容,更多关于Java ThreadLocal的使用的资料请关注其它相关文章!


推荐阅读
  • Docker的安全基准
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
  • 2018年3月31日,CSDN、火星财经联合中关村区块链产业联盟等机构举办的2018区块链技术及应用峰会(BTA)核心分会场圆满举行。多位业内顶尖专家深入探讨了区块链的核心技术原理及其在实际业务中的应用。 ... [详细]
  • 云计算的优势与应用场景
    本文详细探讨了云计算为企业和个人带来的多种优势,包括成本节约、安全性提升、灵活性增强等。同时介绍了云计算的五大核心特点,并结合实际案例进行分析。 ... [详细]
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 题库来源:安全生产模拟考试一点通公众号小程序G3锅炉水处理报名考试是安全生产模拟考试一点通生成的,G3锅炉水处理证模拟考试题库是根据G3锅炉水处理最新 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • 本文详细介绍了网络存储技术的基本概念、分类及应用场景。通过分析直连式存储(DAS)、网络附加存储(NAS)和存储区域网络(SAN)的特点,帮助读者理解不同存储方式的优势与局限性。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • 本文探讨了Java编程的核心要素,特别是其面向对象的特性,并详细介绍了Java虚拟机、类装载器体系结构、Java类文件和Java API等关键技术。这些技术使得Java成为一种功能强大且易于使用的编程语言。 ... [详细]
author-avatar
横店东磁何静
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有