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区别是什么呢
synchronized | ThreadLocal |
---|---|
以时间换空间的方式,只提供一个变量锁,让不同的线程去排队访问。侧重于多个线程访问共享资源的同步 | 以空间换时间的方式,为每个线程都提供了一个变量,从而实现互不干扰。侧重于多个线程让每个线程之间的数据相互隔离。 |
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);
}
}