01入门-ThreadLocal详解-并发编程(Java)
创始人
2024-03-25 21:36:52

文章目录

    • 1 简介
    • 2 基本使用
      • 2.1 常用方法
      • 2.2 小案例
    • 3 ThreadLocal与Sycronized
    • 4 应用场景
      • 4.1 转账案例构建
      • 4.2 问题
      • 4.3 解决
    • 5 后记

1 简介

官方JDK源码关于ThreadLocal描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法)能保证各个线程的局部变量独立其他线程的局部变量。ThreadLocal实例通常都是某些类的私有静态内部成员,用于关联线程和现场上下文。

ThreadLocal的作用:提供线程内局部变量,不同现场间不会相互干扰;线程局部变量在线程的生命周期内起作用;减少同一个线程内多个函数或者组件之间一些共享变量传递的复杂性。

总结:

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

2 基本使用

2.1 常用方法

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

2.2 小案例

  • 要求:对于一个类成员变量,每个线程各自获取或者设置自己的值即实现线程隔离;单个线程设置和获取同一个变量

初始代码如下:

/*** @author Administrator* @date 2022-12-04 13:13*/
public class MyDemo {private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}public static void main(String[] args) throws InterruptedException {MyDemo myDemo = new MyDemo();for (int i = 0; i < 5; i++) {Thread thread = new Thread(() -> {myDemo.setContent(Thread.currentThread().getName() + "的数据");System.out.println("---------------");System.out.println(Thread.currentThread().getName() + "----->" + myDemo.getContent());});thread.setName("线程" + i);thread.start();}}
}// 测试结果
---------------
---------------
---------------
线程1----->线程2的数据
线程2----->线程2的数据
---------------
线程4----->线程4的数据
线程0----->线程4的数据
---------------
线程3----->线程3的数据

用ThreadLocal改造上述代码如下:

/*** @author Administrator* @date 2022-12-04 13:13*/
public class MyDemo {private String content;ThreadLocal tl = new ThreadLocal<>();public String getContent() {
//        return content;return tl.get();}public void setContent(String content) {
//        this.content = content;tl.set(content);}public void clear() {tl.remove();}public static void main(String[] args) throws InterruptedException {MyDemo myDemo = new MyDemo();for (int i = 0; i < 5; i++) {Thread thread = new Thread(() -> {myDemo.setContent(Thread.currentThread().getName() + "的数据");System.out.println("---------------");System.out.println(Thread.currentThread().getName() + "----->" + myDemo.getContent());});thread.setName("线程" + i);thread.start();
//            thread.join();}
//        System.out.println(myDemo.getContent());myDemo.clear();}
}// 测试结果
---------------
---------------
---------------
线程0----->线程0的数据
线程1----->线程1的数据
---------------
线程4----->线程4的数据
---------------
线程3----->线程3的数据
线程2----->线程2的数据

3 ThreadLocal与Sycronized

上面的测试用例用Syncronized能不能解决呢?答案是肯定的,但是有没有区别呢,用例相对简单这里不在给出Syncronized实现,下面分析ThreadLocal和Syncronized的不同,如下表3-1所示:

SyncronizedThreadLocal
原理同步机制采用用时间换空间的方式,只提供一份变量,让不同线程排队访问ThreadLocal采用以空间换时间的方式,为每一个线程提供一份变量副本,从而实现同时访问而不相互干扰
侧重点多个线程访问共享资源的同步多个线程中让每个线程之间的数据相互隔离
效果共享资源一致性,性能损耗,失去并发性每个线程之间数据隔离,每个线程在不同方法之间数据的传递,一致性
  • 总结:在刚刚的案例中,虽然使用ThreadLocal和Syncronized都能解决问题,但是使用ThreadLocal更为合适,因为这样使程序拥有更高的并发性。

4 应用场景

通过以上内容,我们已经基本了解ThreadLocal的特点,下面我们 通过一个案例,来看下ThreadLocal的应用场景:事务操作。

4.1 转账案例构建

  • 构建转账场景:有一个数据表,有2个用户张三和李四,张三给李四转账。

  • 设计一些知识:mysql数据库,JDBC和c3p0连接池,maven项目。

  • 项目结构如下:在这里插入图片描述

  • 功能简介

    • AccountDao实现了转出、转入及关闭操作
    • AccountService:转账和释放资源
    • JdbcUtils:获取连接、事务提交、事务回滚、释放连接

4.2 问题

在多线程环境下转账我们要保证数据完整性或者一致性,比如张三账户1000,例如账户1000,无论如何转账,总的资产为2000,但是实际会遇到什么情况呢?

  • 由于异常原因程序未完整运行导致数据不完整
  • 多线程并发导致数据库不完整性

4.3 解决

对于第一种情况,我们需要开启事务。开启事务需要保证在多线程环境下,在一次完整转账操作中数据库连接为同一个,在多个操作下连接可共享。多次转账间数据库连接的隔离,不相互干扰。

对于第二种情况设计数据库加锁问题,这里我们不做讨论。

通过上面的讨论,我们可以使用ThreadLocal来解决第一种情况,具体涉及数据库连接获取的代码如下:

private static final ComboPooledDataSource ds = new ComboPooledDataSource();
/*** 获取连接*  1. 直接从当前线程获取绑定的连接*  2. 如果连接对象为空*      2.1 在去连接池去获取连接*      2.2 将此对象绑定到当前线程*/
static ThreadLocal tc = new ThreadLocal<>();/*** 获取数据库连接* @return  数据库连接* @throws SQLException sql异常*/
public static Connection getConnection() throws SQLException {Connection conn = tc.get();if (conn == null) {conn = ds.getConnection();tc.set(conn);}return conn;
}
  • 第一次获取连接为空的情况下
    • 从数据库连接池获取连接
    • 把连接对象绑定到当前线程
  • 非第一次直接当前线程获取本地变量

通过在转账中添加/0算术异常来模拟异常情况,测试结果如下:

java.lang.ArithmeticException: / by zeroat com.gaogzhen.service.AccountService.transfer(AccountService.java:32)at com.gaogzhen.web.AccountWeb.main(AccountWeb.java:18)
转账失败
Account(id=1, name=张三, balance=1000.00)
Account(id=2, name=李四, balance=1000.00)

完成的代码见文章末尾代码仓库地址。

5 后记

如有问题,欢迎交流讨论。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent

参考:

[1]黑马程序员Java基础教程由浅入深全面解析threadlocal[CP/OL].2020-03-24.

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...