ThreadLocal类与synchronize关键字区别----一个简单示例

ThreadLocal类与synchronize关键字区别

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwlPuIsa-1667724612294)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221106115043161.png)]

下面用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转账。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gYEbEqi-1667724612295)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221106121719860.png)]

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

目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Epu373P-1667724612295)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221106125524870.png)]

案例中的转账涉及两个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

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--连接参数-->
    <default-config>
        <property name="user">root</property>
        <property name="password">redhat</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test?useSSL=false</property>

    <!--连接池参数-->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">3000</property>
    </default-config> <!-- This app is massive! -->

</c3p0-config>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UpIusme-1667724612296)(C:\Users\20745\AppData\Roaming\Typora\typora-user-images\image-20221106154023422.png)]

由于有异常发生,转账过程需要开启事务。但开启事务会出现以下问题:

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

以上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);
    }
}

总结常规解决方案的弊端:

  1. 提高代码耦合度
  2. 降低程序性能

ThreadLocal解决方案

代码改造:只需要将JdbcUtils进行改变

 static ThreadLocal<Connection>   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方案有突出的优势:

  1. 传递数据:保存每一个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
  2. 线程隔离:各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值