剑指JUC原理-15.ThreadLocal(上)

简介: 剑指JUC原理-15.ThreadLocal

ThreadLocal介绍


官方介绍


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


我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。


总结:


  1. 线程并发: 在多线程并发的场景下
  2. 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  3. 线程隔离: 每个线程的变量都是独立的,不会互相影响


基本使用


常用方法


在使用之前,我们先来认识几个ThreadLocal的常用方法

方法声明 描述
ThreadLocal() 创建ThreadLocal对象
public void set( T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量


使用案例


我们来看下面这个案例 , 感受一下ThreadLocal 线程隔离的特点:

public class MyDemo {
    private String content;
    private String getContent() {
        return content;
    }
    private void setContent(String content) {
        this.content = content;
    }
    public static void main(String[] args) {
        MyDemo demo = new MyDemo();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-----------------------");
                System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

打印结果:

从结果可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。下面我们来看下采用 ThreadLocal 的方式来解决这个问题的例子。

public class MyDemo1 {
    private static ThreadLocal<String> tl = new ThreadLocal<>();
    private String content;
    private String getContent() {
        return tl.get();
    }
    private void setContent(String content) {
         tl.set(content);
    }
    public static void main(String[] args) {
        MyDemo demo = new MyDemo();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-----------------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

打印结果:

从结果来看,这样很好的解决了多线程之间数据隔离的问题,十分方便。


ThreadLocal类与synchronized关键字


synchronized同步方式


这里可能有的朋友会觉得在上述例子中我们完全可以通过加锁来实现这个功能。我们首先来看一下用synchronized代码块实现的效果:

public class Demo02 {
    private String content;
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public static void main(String[] args) {
        Demo02 demo02 = new Demo02();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    synchronized (Demo02.class){
                        demo02.setContent(Thread.currentThread().getName() + "的数据");
                        System.out.println("-------------------------------------");
                        String content = demo02.getContent();
                        System.out.println(Thread.currentThread().getName() + "--->" + content);
                    }
                }
            };
            t.setName("线程" + i);
            t.start();
        }
    }
}

打印结果:

从结果可以发现, 加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是多线程共享数据的问题, 在这个案例中使用synchronized关键字是不合适的。


ThreadLocal与synchronized的区别


虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。

synchronized ThreadLocal
原理 同步机制采用’以时间换空间’的方式, 只提供了一份变量,让不同的线程排队访问 ThreadLocal采用’以空间换时间’的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰
侧重点 多个线程之间访问资源的同步 多线程中让每个线程之间的数据相互隔离

总结:


在刚刚的案例中,虽然使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。


运用场景_事务案例


通过以上的介绍,我们已经基本了解ThreadLocal的特点。但是它具体是运用在什么场景中呢? 接下来让我们看一个案例: 事务操作。


转账案例


场景构建


这里我们先构建一个简单的转账场景: 有一个数据表account,里面有两个用户Jack和Rose,用户Jack 给用户Rose 转账。


案例的实现主要用mysql数据库,JDBC 和 C3P0 框架。

dao层代码 : AccountDao

public class AccountDao {
public void out(String outUser, int money) throws SQLException {
    String sql = "update account set money = money - ? where name = ?";
    Connection conn = JdbcUtils.getConnection();
    PreparedStatement pstm = conn.prepareStatement(sql);
    pstm.setInt(1,money);
    pstm.setString(2,outUser);
    pstm.executeUpdate();
    JdbcUtils.release(pstm,conn);
}
public void in(String inUser, int money) throws SQLException {
    String sql = "update account set money = money + ? where name = ?";
    Connection conn = JdbcUtils.getConnection();
    PreparedStatement pstm = conn.prepareStatement(sql);
    pstm.setInt(1,money);
    pstm.setString(2,inUser);
    pstm.executeUpdate();
    JdbcUtils.release(pstm,conn);
}
} 

service层代码 : AccountService

public class AccountService {
    public boolean transfer(String outUser, String inUser, int money) {
        AccountDao ad = new AccountDao();
        try {
            // 转出
            ad.out(outUser, money);
            // 转入
            ad.in(inUser, money);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

工具类 : JdbcUtils

public class JdbcUtils { 
public static void commitAndClose(Connection conn) {
    try {
        if(conn != null){
            //提交事务
            conn.commit();
            //释放连接
            conn.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
public static void rollbackAndClose(Connection conn) {
    try {
        if(conn != null){
            //回滚事务
            conn.rollback();
            //释放连接
            conn.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
} 


引入事务


案例中的转账涉及两个DML操作: 一个转出,一个转入。这些操作是需要具备原子性的,不可分割。不然就有可能出现数据修改异常情况。

public class AccountService {
    public boolean transfer(String outUser, String inUser, int money) {
        AccountDao ad = new AccountDao();
        try {
            // 转出
            ad.out(outUser, money);
            // 模拟转账过程中的异常
            int i = 1/0;
            // 转入
            ad.in(inUser, money);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

所以这里就需要操作事务,来保证转出和转入操作具备原子性,要么同时成功,要么同时失败。


JDBC中关于事务的操作的api

Connection接口的方法 作用
void setAutoCommit(false) 禁用事务自动提交(改为手动)
void commit(); 提交事务
void rollback(); 回滚事务

开启事务的注意点:


  • 为了保证所有的操作在一个事务中,案例中使用的连接必须是同一个: service层开启事务的connection需要跟dao层访问数据库的connection保持一致
  • 线程并发情况下, 每个线程只能操作各自的 connection


剑指JUC原理-15.ThreadLocal(中):https://developer.aliyun.com/article/1413656

目录
相关文章
|
SQL 分布式计算 数据处理
【Hive】请说明hive中 Sort By,Order By,Cluster By,Distrbute By各代表什么意思?
【4月更文挑战第17天】【Hive】请说明hive中 Sort By,Order By,Cluster By,Distrbute By各代表什么意思?
|
机器学习/深度学习 算法 PyTorch
算法金 | 这次终于能把张量(Tensor)搞清楚了!
本文是关于PyTorch中张量(Tensor)的入门教程,由全网同名\[算法金\]作者撰写。文章介绍了张量的基础概念,强调其在深度学习中的核心地位,并阐述了张量与向量、矩阵的关系。接着,详细讲解了如何在PyTorch中创建和操作张量,包括张量的数学运算、广播机制、索引切片以及变形与重塑。此外,还涉及张量的高级功能,如自动求导系统和高级数学函数。最后,文章提到了张量在深度学习中的应用、性能优化技巧和调试方法,鼓励读者通过实践提升技能。
1730 1
算法金 | 这次终于能把张量(Tensor)搞清楚了!
|
5天前
|
云安全 人工智能 安全
AI被攻击怎么办?
阿里云提供 AI 全栈安全能力,其中对网络攻击的主动识别、智能阻断与快速响应构成其核心防线,依托原生安全防护为客户筑牢免疫屏障。
|
15天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
9天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
586 212
|
4天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
233 138
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
825 60
|
7天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1168 157