区别:虽然ThreadLocal模式与synchronize关键字都用于处理多线程并发访问变量,不过两者处理问题的角度和思路不同
下面用synchronize和ThreadLocal分别实现同样的功能
synchronize的代码示例:
public class MyDemo02 {
//变量
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo02 demo01 =new MyDemo02();
//匿名内部类与lambda表达式简写
for (int i=0;i<5;i++){
Thread thread=new Thread(() -> {
synchronized (MyDemo02.class) {
/*
* 每一个线程:存一个变量,过一会取出这个变量
* */
demo01.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("================================");
System.out.println(Thread.currentThread().getName() + "--->" + demo01.getContent());
}
});
thread.start();
}
}
}
ThreadLocal代码示例:
public class MyDemo01 {
ThreadLocal<String> t1= new ThreadLocal<>();
//变量
// private String content;
public String getContent() {
// return content;
//将变量和线程进行绑定
String content=t1.get();
return content;
}
public void setContent(String content) {
t1.set(content);
}
public static void main(String[] args) {
MyDemo01 demo01 =new MyDemo01();
//匿名内部类与lambda表达式简写
for (int i=0;i<5;i++){
Thread thread=new Thread(() -> {
/*
* 每一个线程:存一个变量,过一会取出这个变量
* */
demo01.setContent(Thread.currentThread().getName()+"的数据");
System.out.println("================================");
System.out.println(Thread.currentThread().getName()+"--->"+demo01.getContent());
});
thread.start();
}
}
}
用一个案例去了解ThreadLocal具体运用在什么场景中。
转账案例
场景构建:
这里我们先构建一个简单的转账场景:有一个数据表account,里面有两个用户Jack和Rose,用户Jack给Rose转账。
案例的实现主要用mysql数据库,jdbc和c3p0框架。
目录结构:
案例中的转账涉及两个DML操作:一个转出,一个转入。这些操作是需要具备原子性的,不可分割。不然就有可能出现数据修改异常情况。
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);
}
}
AccountService:
public class AccountService {
//转账
public boolean transfer(String outUser, String inUser, int money) {
AccountDao ad = new AccountDao();
Connection conn = null;
try {
//1.开启事务
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);
//转出
ad.out(outUser, money);
//算数异常:模拟转出成功,转入失败
int i=1/0;
//转入
ad.in(inUser,money);
//2.成功提交
JdbcUtils.commitAndClose(conn);
} catch (Exception e) {
e.printStackTrace();
//2.失败回滚
JdbcUtils.rollbackAndClose(conn);
return false;
}
return true;
}
}
JdbcUtils:
public class JdbcUtils {
//c3p0 数据库连接池对象属性
private static final ComboPooledDataSource ds = new ComboPooledDataSource();
//获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//释放资源
public static void release(AutoCloseable... ios) {
for (AutoCloseable io : ios) {
if (io != null) {
try {
io.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
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 (Exception e){
e.printStackTrace();
}
}
}
AccountWeb:
public class AccountWeb {
public static void main(String[] args){
//模拟数据 :Jack给Rose转账100
String outUser="Jack";
String inUser="Rose";
int money=100;
AccountService as =new AccountService();
boolean result = as.transfer(outUser,inUser,money);
if (!result){
System.out.println("转账失败!");
}else {
System.out.println("转账成功!");
}
}
}
c3p0-config.xml
<c3p0-config>
<default-config>
<property name="user">rootproperty>
<property name="password">redhatproperty>
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test?useSSL=falseproperty>
<property name="initialPoolSize">5property>
<property name="maxPoolSize">10property>
<property name="checkoutTimeout">3000property>
default-config>
c3p0-config>
由于有异常发生,转账过程需要开启事务。但开启事务会出现以下问题:
以上2个问题:常规的解决方案
* 1.传参:将service层的connection对象直接传递dao层
* 2.加锁
更改后的AccountService:
public class AccountService {
//转账
public boolean transfer(String outUser, String inUser, int money) {
AccountDao ad = new AccountDao();
Connection conn = null;
try {
synchronized (AccountService.class){
//1.开启事务
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);
//转出
ad.out(outUser, money,conn);
//算数异常:模拟转出成功,转入失败
int i=1/0;
//转入
ad.in(inUser,money,conn);
//2.成功提交
JdbcUtils.commitAndClose(conn);
}
} catch (Exception e) {
e.printStackTrace();
//2.失败回滚
JdbcUtils.rollbackAndClose(conn);
return false;
}
return true;
}
}
AccountDao:
public class AccountDao {
//转出
public void out(String outUser, int money, Connection conn)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, Connection conn) 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);
}
}
总结常规解决方案的弊端:
ThreadLocal解决方案
代码改造:只需要将JdbcUtils进行改变
static ThreadLocal t1=new ThreadLocal<>();
//获取连接
/*
* 原本:直接从连接池中获取连接
* 现在:
* 1.直接获取当前线程绑定的连接对象
* 2.如果连接对象是空的
* 2.1再去连接池中获取连接
* 2.2将此连接对象跟当前线程进行绑定
* */
public static Connection getConnection() throws SQLException {
Connection conn = t1.get();
if (conn == null){
conn = ds.getConnection();
t1.set(conn);
}
return conn;
// return ds.getConnection();
}
总结:在ThreadLocal方案有突出的优势: