ThreadLocal和线程上下文-入门

ThreadLocal和线程上下文

经过学习和理解,总结了以下经验:

真正的主角是线程上下文,而不是ThreadLocal

ThreadLocal的本质是实现Java线程私有map变量的一个工具类,所以,提供的最核心的方法就是set和get

如果引入线程上下文的概念,那么,ThreadLocal就是实现线程上下文功能的工具类

摘要

本文主要的目的是实现ThreadLocal的入门,基于自己的理解给出一个比较容易懂的ThreadLocal定义,并且结合线程上下文的概念,给出ThreadLocal的定位。本文主要包括以下内容:

  1. ThreadLocal的简单定义和理解
  2. 结合线程上下文的概念,总结了ThreadLocal的2大特性,特性决定了其用途
  3. 简单介绍了ThreadLocal在开发中的常见使用场景,分别使用了其私有特性和共享特性
入门
定义

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Threadlocal 提供了线程本地私有的变量,通过get和set方法可以对变量进行操作,每个线程之间的Thread-Local变量是相互独立的。一般来说,ThreadLocal都是私有静态属性,用来存储一些需要和线程绑定的数据,比如事务ID,用户ID。

Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the {@code ThreadLocal}
instance is accessible; after a thread goes away, all of its copies of
thread-local instances are subject to garbage collection (unless other
references to these copies exist).

每个线程持有对ThreadLocal变量的引用,只要线程存在,这个变量就一直存在。如果线程对象被回收,则Thread-Local 变量在没有别的引用的情况下也会被回收。

注意,ThreadLocal≠thread-local变量,在后面的描述中可以发现,ThreadLocal和thread-local变量是key-value的关系,共同存在Thread维护的map中。

小结:

  1. java实现线程私有变量的逻辑非常简单,就是在每个线程中维护一个map,所以,这个map自然就是线程私有的,每个线程只会访问自己的map。
  2. ThreadLocal相当于一个工具类,用来实现这个私有map的功能。
  3. 所以,给ThreadLocal的定位,就是实现Thread私有map的工具类。

所以,从本质上讲,就要把ThreadLocal理解成一个工具类,它并不存储数据,只是提供了让线程存储私有数据的方法。

为什么ThreadLocal难以理解?

根据上面的分析,在理解ThreadLocal时最容易犯的错误就是:

  1. 以为数据是存在ThreadLocal中
  2. 没有找准ThreadLocal的定位,没有梳理清楚ThreadLocal和Thread之间的关系

所以,要理解ThreadLocal,首先要下一个自己能够理解的定义,目前来说,我对于ThreadLocal的定义就是:

ThreadLocal就是一个工具类,为Thread对象提供了私有变量的操作方法

基于这个定义,就可以从工具类的角度去理解,而不需要关心各种key-value关系,理解起来还比较顺畅。

示例代码

使用ThreadLocal编程的思路和一般使用工具类没有太大的区别

  1. 首先定义N个ThreadLocal对象,需要多少个私有变量,就定义多少个。
  2. 然后就是调用ThreadLocal对象提供的工具方法,在每个线程的代码中进行数据的存和取即可。

在编程的过程中,重点不要放在ThreadLocal上,而应该放在它提供的set和get方法以及线程的私有变量逻辑上

public class ThreadLocalDemo {
    public static final ThreadLocal<String> tl = new ThreadLocal<String>();
    public static void main(String[] args) {
        //创建一个线程,操作私有变量
        Thread t1 = new Thread(() -> {
            tl.set("线程1私有变量");
            System.out.println(Thread.currentThread() + "---" + tl.get());
        });
        t1.start();
      	//set方法在哪里执行,私有变量就存到哪个线程对象中
        tl.set("主线程私有变量");
        System.out.println(Thread.currentThread() + "---" + tl.get());
    }
}
ThreadLocal和线程上下文的特性

真正有价值的是线程上下文,ThreadLocal只是实现线程上下文的一种方式而已

  1. 线程上下文共享特性:对于同一个线程的所有方法,能够用线程上下文共享变量,不需要进行参数传递。这个特性特别适用于token、session、httpHeader等参数
  2. 线程私有特性:避免上下文变量不一致或者多个线程共享变量
使用场景

特性决定用途,共享特性和私有特性支持了线程上下文用于事务、全局session等场景。

在事务场景中使用ThreadLocal

在事务场景中使用,利用的是线程私有的特性

如果连接池,在事务的场景中,在Service层需要开启事务和关闭事务,在DAO层需要基于数据库连接操作数据。在Service和DAO中,必须使用同一个连接,不然就无法控制事务。

如果不加任何控制,在Service层和DAO层获取到的连接对象可能都不是同一个,所以,在单次业务流程中,是一个线程执行的,每个线程必须独享一个连接,直到业务结束。

因此,在获取连接的方法中,需要将连接放到本线程的私有map中,使用ThreadLocal刚好可以实现。

注意:

  1. 在Service和DAO的代码中不需要关心ThreadLocal,只需要在获取连接的方法中添加ThreadLocal的逻辑即可。
  2. Service、DAO、获取连接的方法,必须是串行的,不能使用异步,否则就不是同一个线程了
在业务代码中传递Session/Token等共享信息

在session和token场景中使用ThreadLocal使用的是共享特性,由于所有的方法中都有可能要用session,所以使用上下文共享特性,可以简化代码,这里不是为了私有,而是为了共享。

大致流程如下:

  1. 在SessionContext中,首先使用set操作把用户信息放到当前线程私有map中
  2. 然后在所有的方法中,就可以直接取出用户信息,不需要手动传递参数,简化了代码
ThreadLocal内存泄漏问题
简单原理分析
  1. 在ThreadLocal使用场景中,一共有3种对象,分别是Thread,ThreadLocalMap,ThreadLocal,相互的关系是:Thread中维护一个ThreadLocalMap,ThreadLocalMap的key就是ThreadLocal。
  2. 由于我们大部分场景是线程池,从而,map的生命周期也会很长,进而map中的key和value就一直会被某个entry引用。
  3. java的设计中,key到ThreadLocal的引用是弱引用,当ThreadLocal弹出栈之后,ThreadLocal对象就会被回收,所以,key不会导致内存泄漏。
  4. 但是entry的value到存储的变量之间是强引用,如果不删除entry,则一直会有entry指向存储的obj,这个obj就会导致内存泄漏。从设计上,这个obj的生命周期必须和ThreadLocal相同,但是如果不手动清理,就会一直存在,导致内存泄漏

示例图如下:参考链接

在这里插入图片描述

小结:

  1. 内存泄漏的根本原因在于线程的生命周期是很长的,而value是存在线程对象的map中的,所以value的生命周期默认也很长了。
ThreadLocal使用完一定要手动删除变量
  1. 由于可能存在内存泄漏问题,所以当ThreadLocal失效后,必须同步remove thread-local私有变量。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值