ThreadLocal类及spring模拟多用户转账

1.ThreadLocal类的作用

如果我们在多线程情况下不使用ThreadLoacl类:

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) {
        MyDemo myDemo = new MyDemo();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    myDemo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-------------------------------------");
                    System.out.println(Thread.currentThread().getName() + "------->" + myDemo.getContent());
                }
            });
            t.setName("线程"+i);
            t.start();
        }
    }
}

运行结果:
在这里插入图片描述
可以看出当前线程可能取出了另外一个线程的数据。
ThreadLoal就是解决这种情况的,在多线程的场景下,通过提供线程内的局部变量,让每个线程都是相互独立的,不会相互干扰。

2.基本使用

常用方法

在这里插入图片描述
使用ThreadLocal类:

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

运行结果:
在这里插入图片描述

ThreadLocal内部实现

底层其实就是一个map集合,在调用set方法的时候,会自动将key值设置成当前线程对象,value则是你传进去的参数值。调用get方法的时候,会按照当前线程值作为key值获取到对应的value。

3.那么ThreadLocal和synchronized区别是什么呢

synchronizedThreadLocal
以时间换空间的方式,只提供一个变量锁,让不同的线程去排队访问。侧重于多个线程访问共享资源的同步以空间换时间的方式,为每个线程都提供了一个变量,从而实现互不干扰。侧重于多个线程让每个线程之间的数据相互隔离。

4.运用场景:事务管理

例如转账、简化就是进行加钱和减钱的操作。若同时有不同用户去转账,那么就需要去控制每个线程对应的数据,就是实现每个线程相互独立,互不干扰。
现在有个场景就是,多个用户去从druid连接池中获取connection连接,那么如果两个线程同时抢占一个连接怎么办呢?所以使用ThreadLocal类为每一个线程分配一个自己的连接对象。
那么我们就来实现一个简单转账小功能:
①导入依赖:

<dependencies>
	<!--mysql驱动-->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.47</version>
	</dependency>
	<!--druid连接池-->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>1.1.15</version>
	</dependency>
	<!--dbUtils工具类-->
	<dependency>
		<groupId>commons-dbutils</groupId>
		<artifactId>commons-dbutils</artifactId>
		<version>1.6</version>
	</dependency>
	<!--spring核心-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>5.1.5.RELEASE</version>
	</dependency>
	<!--junit-->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
	</dependency>
	<!--spring整合junit-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>5.1.5.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.10</version>
	</dependency>
</dependencies>
<!--jdk版本锁定-->
<build>
	<plugins>
		<!-- 设置编译版本为1.8 -->
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
				<encoding>UTF-8</encoding>
			</configuration>
		</plugin>
	</plugins>
</build>

②Account实体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Integer id;
    private String name;
    private Double money;
}

③AccountDao接口和实现

public interface AccountDao {
    // 转出
    void outAccount(String outUser, Double money);
    // 转入
    void inAccount(String inUser, Double money);
}

在这里插入图片描述

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void outAccount(String outUser, Double money) {
        try {
            String sql = "update account set money = money - ? where name = ?";
            //可以手动控制事务
            queryRunner.update(connectionHolder.getConnection,sql, money,outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void inAccount(String inUser, Double money) {
        try {
            String sql = "update account set money = money + ? where name = ?";
            //可以手动控制事务
            queryRunner.update(connectionHolder.getConnection,sql, money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

★ConnectionHolder工具类,目的就是实现线程内对象的共享

@Component
public class ConnectionHolder {

    @Autowired
    private DataSource dataSource;

    private static final ThreadLocal<Connection> TL = new ThreadLocal<>();

    // 向线程内绑定 conn
    public void setConnection() {
        try {
            TL.set(dataSource.getConnection());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 从线程内获得 conn
    public Connection getConnection() {
        if (TL.get() == null) {
            setConnection();
        }
        return TL.get();
    }

    // 解除线程绑定 conn
    public void removeConnection() {
        TL.remove();
    }
}

★TransactionManager事务管理类,目的就是将service重复的事务代码抽取到工具类

@Component
public class TransactionManager {

    @Autowired
    private ConnectionHolder connectionHolder;

    // 开启事务
    public void begin(){
        // 将connection绑定到当前线程内
        connectionHolder.setConnection();
        // 从线程内获取conn
        Connection connection = connectionHolder.getConnection();
        try {
            // 开启事务
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }



    // 提交事务
    public void commit(){
        // 从线程内获取conn
        Connection connection = connectionHolder.getConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    // 回滚事务
    public void rollback(){
        // 从线程内获取conn
        Connection connection = connectionHolder.getConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    // 释放资源
    public void release(){
        // 从线程内获取conn
        Connection connection = connectionHolder.getConnection();
        connectionHolder.removeConnection(); //线程解除
        try {
            connection.close();// 释放资源
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

④AccountService接口和实现

public interface AccountService {
    // 转账
    void transfer(String outUser, String inUser, Double money);
}
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ConnectionHolder connectionHolder;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String outUser, String inUser, Double money) {

        try {
            // 开启事务
            transactionManager.begin();

            // 业务操作 start
            accountDao.outAccount(outUser, money); // 转出

          //  int i = 1 / 0; // 制造bug

            accountDao.inAccount(inUser, money); // 转入
            // 业务操作 end

            // 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback();
        } finally {
            // 释放资源
            transactionManager.release();
        }

    }
}

⑤ spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
       	http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
       	http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--开启注解组件扫描-->
    <context:component-scan base-package="com.itheima"/>
    
    <!--加载第三方properties-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!--第三方连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    
    <!--第三方queryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
</beans>

⑥ 测试类

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class AccountTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void test01() throws Exception {
        accountService.transfer("tom", "jack", 200d);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值