JDK ThreadLocal 源码深度剖析及注意点分享

概述

ThreadLocal 顾名思义,就是“线程局部”的意思,换句话说就是属于某个线程的局部对象,其他线程是没法访问到的,亦即该对象不存在线程安全的问题,因为不可能被多线程访问到,这个是理解 ThreadLocal 的大前提。这篇文章将结合源码,从以下几方面做些分享

  • 使用场景举例
  • 源码剖析
  • 可能存在的坑

使用场景举例

大家在使用数据库进行 java 代码编写的应用的时候,始终绕不开一个话题,那就是事务。比如说咱们在往数据库里面插入一条数据或者修改一条数据的某个字段时,我们在操作成功之后,必须要先提交事务,才能确保该数据真正被更新到数据库,这样应用程序的其他线程就能看到当前的最新数据了。那要做到这一点,我们怎么办到呢?为了更好的说明场景,我们举一个更为具体的例子。如用户注册,大概的流程如下:<br>

  • 用户在前端提交注册请求
  • 后端 service 层代码进行业务逻辑处理
  • service 调用 DAO 层将数据插入到数据库
  • service 通过判断 DAO 的插入结果给前端返回注册结果

上述过程的一个可能实现的伪代码如下

public class UserController{

    public Result<Object> userRegister(User user){
        ...
        UserService service = new UserService();
        return service.doRegister(user);
    }
}

public class UserService{

    public Result<Object> doRegister(User user){
        // 可能需要进行一些相关的验证
        ...
        UserBo bo = transfer(user);
        TransactionManager.begin(); // 开启事务
        UserDao dao = new UserDao();
        try{
            dao.save(bo);
            TransactionManager.commit(); // save 成功,提交事务
        }catch(Exception e){
            TransactionManager.rollback(); // save 异常,回滚事务
        }
        ...
        // 根据结果返回相应的注册结果
    }
}

// 数据库事务管理器
public class TransactionManager{

    private static ThreadLocal<Session> sessionLocal = new ThreadLocal<>();

    public static Session begin(){
        Session s = sessionLocal.get();
        // 如果当前线程中不存在事务,则 new 一个,否则使用之前的事务(当然这种策略是可以改的,Spring 的事务管理中就支持可配置事务的传播特性)
        if(s == null){
            s = new Session();
            sessionLocal.set(s);
        }
        
        s.open();
        return s;
    }

    public static void commit(){
        Session s = sessionLocal.get();
        if(s != null){
            s.commit();
            s.close();
            sessionLocal.remove();
        }

        throw new TransactionException("Transaction state error.");
    }

    public static void rollback(){
        Session s = sessionLocal.get();
        if(s != null){
            s.rollback();
            s.close();
            sessionLocal.remove();
        }
        
        throw new TransactionException("Transaction state error.");
    }
}

代码特别简单易懂,就不逐一说明了。从上面的代码看的出来,我们在事务管理器里面实际上就用到了 ThreadLocal 来保存我们的数据库 session,用于事务的开始、提交或回滚。如果不了解 ThreadLocal 的同学可能会问了,你这个是个静态变量,如果在多线的环境下运行,不是多个线程同时访问了同一个 ThreadLocal 对象了么?那是不是有可能,在线程 A 中开启的事务,被线程 B 给 commit 或 rollback 了?答案当然是否定的。这就是 ThreadLocal 的设计巧妙之处了。带着这样的疑问,我们通过源码一窥究竟。

源码剖析

关键类和以及各类之间的关系

一说到 ThreadLocal 就不得不提 Thread、ThreadLocalMap,因为这三者是紧密联系在一起的,他们之间的关系如下图

 

从上图中可以看到,Thread 中有个 ThreadLocalMap 的变量 threadLocals,用于保存当前线程中几乎所有的 ThreadLocal 对象(之所有不是全部,是因为实际上线程还有可能从父线程继承一些 ThreadLocal 对象过来,并且保存在ThreadLocal.ThreadLocalMap inheritableThreadLocals这个变量中),而 ThreadLocalMap 是 ThreadLocal 的一个内部类,并且 ThreadLocal 对象与 ThreadLocal 对象所代表值实际上都是以<ThreadLocal, Object>这样的键值对的形式保存在 ThreadLocalMap 中的,并不是把 ThreadLocal 指向的值直接保存在 ThreadLocal 中,在 ThreadLocal 中只有一个 threadLocalHashCode 字段,用于保存当前对象的 hash 值。这样我们对这三者在关系上就有了一个宏观上的了解了。接下来我们重点分析 ThreadLocal 的3个 public 方法,set(T obj), get(), remove() 方法。

ThreadLocal

ThreadLocal.set(T value)

表示往 ThreadLocal 对象中存入某个值,该值并不是保存在 ThreadLocal 对象中的,而是保存在当前线程的 ThreadLocalMap 中。
源码如下

public void set(T valu
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值