进阶良药之ThreadLocal

同学,你是否被面试官考察过这些问题:

  1. 什么是ThreadLocal,能说说你的理解吗?
  2. ThreadLocal类有哪些方法,它们都是做什么的?
  3. ThreadLocal变量和synchronized关键字的区别是什么?
  4. 你能想到哪些使用ThreadLocal的典型场景?
  5. ThreadLocal为什么会导致内存泄露?

上面的五个问题是Java面试中考察ThreadLocal变量时的经典问题。要完美的回答这些问题,咱还得从头说起!下面是JDK文档对ThreadLocal的解释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.
翻译:ThreadLocal类提供线程本地变量。线程本地变量和普通的变量不同,每一个访问(通过get或者set方法)线程本地变量的线程都将拥有一个属于它们自己的独立初始化的副本。

JDK文档说的很清楚,ThreadLocal类用来声明一个线程本地变量。每有一个访问该变量的线程,就会对应创建一个该变量的副本。

面试中,如果你仅仅是这么解释,可能面试官会觉得还不够。是时候聊一聊源码了。下面是ThreadLocal类的部分源码(以set方法为例):

public class ThreadLocal<T> {

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

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new java.lang.ThreadLocal.ThreadLocalMap(this, firstValue);
    }

}

ThreadLocal的set()方法在执行时,会首先使用Thread.currentThread()获取执行当前方法的线程t,然后获取t的成员变量threadLocals。如果threadLocals不存在,则为其创建一个ThreadLocalMap对象并赋值给它;如果threadLocals存在,则直接把要保存的对象放入其中。

从本质上来讲,ThreadLocal更像是一个工具类。我们对ThreadLocal对象的操作最终被转化为对当前线程的threadLocals成员变量的操作。即,对一个ThreadLocalMap对象进行存取操作。

费了“九牛二虎之力”,总算回答了第一个问题。憋急!我们来看第二个问题。

根据JDK文档,ThreadLocal类有四个常用的方法:

T get()

该方法用于返回线程本地变量在当前线程中的值。如果当前线程中没有该变量的值,则先调用initialValue()方法执行初始化,然后再返回对应的值。

T initialValue()

如果当前线程在执行get()方法之前没有执行过set()方法,那么initialValue()方法将被调用,反之initialValue()方法将不会被调用,如果initialValue()方法被调用的时候,将会把null当做value值存在ThreadLocalMap中,之后把null作为get()方法的返回值在返回给你,这个可以直接看源码

void remove()

删除某个线程本地变量在当前线程中的值。如果删除之后,再一次读取该线程本地变量的值,会重新执行initialValue()方法对其进行初始化。

void set(T value)

设置线程本地变量在当前线程中保存的值。

读了上面四个方法的定义,是不是再一次印证了我之前说的?我们可以把ThreadLocal理解为一个工具类,它帮助我们去操作当前线程中的一个Map结构的成员变量。这个Map结构的成员变量是线程私有的。

讲解到这里,第三个问题的答案就呼之欲出了!

ThreadLocal变量和synchronized关键字都是用来解决线程安全问题的,但是两者解决问题的思路完全不同。ThreadLocal使得每个线程都有自己的局部变量资源,因此多个线程之间互不干扰,从而实现线程安全。synchronized关键字保证每个时刻只能有一个线程访问共享变量,从而保证了线程安全。

对于ThreadLocal这么“有料”的知识点,面试官自然要考察下求职者的实战能力了。反正,我是不会错过的。同学,说说哪些场景下会用到ThreadLocal变量?

1. Request粒度的信息保存

Java Web是一个单例多线程的模型。

即通常情况下,Web程序中的每一个Bean都是单例,而用户的每次请求都会对应一个独立的线程,当多个用户同时访问Web程序时,表现出来的就是单例多线程。

在这种模型中,如果需要在一次请求周期中保存某些用户信息,那这个信息绝对不能放到类的成员变量中去。因为类的成员变量是隶属于同一个Bean的,而Bean是被多个线程所共享的,会导致多个线程更改同一个成员变量的情况,程序表现为一会正常一会不正常。这也是实践中经常遇到的bug。

对于这种场景,就要使用ThreadLocal类了。通常的做法是在请求进入Bean之前把相关的信息保存到ThreadLocal变量中,待后续需要的时候从ThreadLocal中获取即可。

2. Spring框架的事务处理

大家都知道,Spring允许以声明的方式进行事务管理。通过声明的方式,程序员可以仅仅专注于业务代码,事务管理由Spring框架代为进行。那么Spring是如何做到这一点的呢?

以JDBC为例,正常的事务代码如下。该代码可以分成三个部分:事务准备阶段(第1~3行)、业务处理阶段(第4~6行)、事务提交阶段(第7行) 。

jdbc = new DataBaseConnection();//第1行
Connection con = jdbc.getConnection();//第2行
con.setAutoCommit(false);//第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
con.commit();//第7行

Spring框架包揽了事务准备阶段和事务提交阶段的代码,使得程序员专注于设计业务处理阶段的代码。Spring框架可以使用AOP(Aspect Oriented Programming)来动态的织入准备阶段和事务提交阶段的代码。但如何才能让三个阶段使用同一个数据源连接呢?这是很重要的。

在这个场景中,我们实际上是希望让软件结构中纵向的三个阶段使用同样的一个参数con,而这三个阶段之间又无法进行显式的参数传递。解决方案是—ThreadLocal。Spring框架使用ThreadLocal记录每个线程在进行事务操作时所使用的数据库连接,在事务准备阶段,将数据库连接放入ThreadLocal中,在业务处理阶段和事务提交阶段直接从ThreadLocal中获取对应的连接即可。这个是ThreadLocal的经典应用。

以上讲解了两个ThreadLocal在实践中的应用。如果在面试中,你不但能够讲清楚ThreadLocal的底层原理,还能够结合具体的实际娓娓道来,比如Spring框架中是如何使用ThreadLocal的。相信面试官会对你大加欣赏!

然后我们回到第5个问题,当你使用ThreadLocal变量久了之后,可能会遇到内存泄露的问题。不要着急,这说明你距离一个老司机只有一步之遥了。

假设我们有这样一段代码:

public class Test {
    public static final ThreadLocal<String> tl = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(tl.get());
            }
        });
        t.start();
    }
}

这段代码执行后,内存中是这样的:

在这里插入图片描述

如上文所述,threadLocals变量实际上是ThreadLocal.ThreadLocalMap类型的对象。它本质上是一个Map结构,只不过这个Map结构中的Key是一个ThreadLocal类型的弱引用。从图中可以看到,tl的存储空间有两个引用,一个是强引用,一个是来自于ThreadLocalMap的弱引用。当线程执行完毕,栈空间被销毁以后,强引用不复存在,而弱引用并不会妨碍GC对tl存储空间的回收,这从一定程度上避免了内存泄露。

但我们也不能忽略一个事实,当tl的存储空间被垃圾回收以后。ThreadLocalMap中原本指向tl的Entry,其key的值将变成NULL。即,内存中将存在一些key为NULL的Entry。这部分Entry也是需要被垃圾回收的。ThreadLocalMap在设计时也考虑到了这些问题,并添加了防护措施:在调用的get()、set()、remove()的时候都会顺便清除线程ThreadLocalMap里面所有key为NULL的Entry。

但是作为JAVA程序员的我们需要清楚,即便Java在设计TheadLocal时做出了这么多预防内存泄露的努力,也还是我们显式调用remove()方法来的更加踏实一些。

所以你永远要记住使用ThreadLocal的最佳实践:

每次使用完ThreadLocal之后,都要调用它的remove()方法,清除数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度行监控,并按照作物对土壤湿度的要求行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不行灌水。
智慧农业是一种结合了现代信息技术,包括物联网、大数据、云计算等,对农业生产过程行智能化管理和监控的新模式。它通过各种传感器和设备采集农业生产中的关键数据,如大气、土壤和水质参数,以及生物生长状态等,实现远程诊断和精准调控。智慧农业的核心价值在于提高农业生产效率,保障食品安全,实现资源的可持续利用,并为农业产业的转型升级提供支持。 智慧农业的实现依赖于多个子系统,包括但不限于设施蔬菜精细化种植管理系统、农业技术资料库、数据采集系统、防伪防串货系统、食品安全与质量追溯系统、应急追溯系统、灾情疫情防控系统、农业工作管理系统、远程诊断系统、监控中心、环境监测系统、智能环境控制系统等。这些系统共同构成了一个综合的信息管理和服务平台,使得农业生产者能够基于数据做出更加科学的决策。 数据采集是智慧农业的基础。通过手工录入、传感器自动采集、移动端录入、条码/RFID扫描录入、拍照录入以及GPS和遥感技术等多种方式,智慧农业系统能够全面收集农业生产过程中的各种数据。这些数据不仅包括环境参数,还涵盖了生长状态、加工保存、检验检疫等环节,为农业生产提供了全面的数据支持。 智慧农业的应用前景广阔,它不仅能够提升农业生产的管理水平,还能够通过各种应用系统,如库房管理、无公害监控、物资管理、成本控制等,为农业生产者提供全面的服务。此外,智慧农业还能够支持政府监管,通过发病报告、投入品报告、死亡报告等,加强农业产品的安全管理和质量控制。 面对智慧农业的建设和发展,存在一些挑战,如投资成本高、生产过程标准化难度大、数据采集和监测的技术难题等。为了克服这些挑战,需要政府、企业和相关机构的共同努力,通过政策支持、技术创新和教育培训等手段,推动智慧农业的健康发展。智慧农业的建设需要明确建设目的,选择合适的系统模块,并制定合理的设备布署方案,以实现农业生产的智能化、精准化和高效化。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值