• 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();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    用一个案例去了解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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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();
                }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    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("转账成功!");
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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.加锁
    
    • 1
    • 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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

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

    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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    总结:在ThreadLocal方案有突出的优势:

    1. 传递数据:保存每一个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
    2. 线程隔离:各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失。
  • 相关阅读:
    STM8的C语言编程(11)--+切换时钟源
    pom.xml中解决“vulnerable dependency maven:org.yaml:snakeyaml:1.33“警告问题
    安装Milvus的可视化工具Attu教程
    django configparser.NoSectionError: No section: ‘Samples
    第四十五章 开发自定义标签 - 规则和操作
    LeetCode #100. 相同的树
    什么是Docker和Docker-Compose?
    PS运行中缺失d3dcompiler_47.dll问题的5个有效修复方法总结
    20年上海站D题Walker(二分,简洁)
    修复 JavaScript 错误的四种方法
  • 原文地址:https://blog.csdn.net/weixin_53050118/article/details/127717770