欧博亚洲app下载:ThreadLocal的使用场景剖析

admin 3个月前 (07-08) 科技 36 1

目录

一.ThreadLocal先容

二.使用场景1——数据库事务问题

  2.1 问题靠山

  2.2 方案1-修改接口传参

  2.3 方案2-使用ThreadLocal

三.使用场景2——日志追踪问题

四.其他使用场景

 

 

一.ThreadLocal先容

  我们知道,变量从作用域局限举行分类,可以分为“全局变量”、“局部变量”两种:

  1.全局变量(global variable),好比类的静态属性(加static要害字),在类的整个生命周期都有用;

  2.局部变量(local variable),好比在一个方式中界说的变量,作用域只是在当前方式内,方式执行完毕后,变量就销毁(释放)了;

  使用全局变量,当多个线程同时修改静态属性,就容易泛起并发问题,导致脏数据;而局部变量一般来说不会泛起并发问题(在方式中开启多线程并发修改局部变量,仍可能引起并发问题);

  再看ThreadLocal,从名称上就能知道,它可以用来保留局部变量,只不过这个“局部”是指“线程”作用域,也就是说,该变量在该线程的整个生命周期中有用。

 

二.使用场景1——数据库事务问题

2.1问题靠山

  下面先容示例,UserService挪用UserDao删除用户信息,涉及到两张表的操作,以是用到了数据库事务:

  数据库封装类DbUtils

public class DbUtils {

    // 使用C3P0毗邻池
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");

    public static Connection getConnectionFromPool() throws SQLException {
        return dataSource.getConnection();
    }

    // 省略其他方式.....
}

  UserService代码如下:  

public class UserService {

    private UserDao userDao;

    public void deleteUserInfo(Integer id, String operator) {
        Connection connection = null;
        try {
            // 从毗邻池中获取一个毗邻
            connection = DbUtils.getConnectionFromPool();
            // 由于涉及事务操作,以是需要关闭自动提交
            connection.setAutoCommit(false);

            // 事务涉及两步操作,删除用户表,增添操作日志
            userDao.deleteUserById(id);
            userDao.addOperateLog(id, operator);

            connection.commit();
        } catch (SQLException e) {
            // 回滚操作
            try {
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException ex) {
            }
        } finally {
            DbUtils.freeConnection(connection);
        }
    }
}

  下面是UserDao,省略了部门代码:

package cn.ganlixin.dao;
import cn.ganlixin.util.DbUtils;
import java.sql.Connection;

/**
 * @author ganlixin
 * @create 2020-06-12
 */
public class UserDao {

    public void deleteUserById(Integer id) {
        // 从毗邻池中获取一个数据毗邻
        Connection connection = DbUtils.getConnectionFromPool();

        // 行使获取的数据库毗邻,执行sql...........删除用户表的一条数据

        // 送还毗邻给毗邻池
        DbUtils.freeConnection(connection);
    }

    public void addOperateLog(Integer id, String operator) {
        // 从毗邻池中获取一个数据毗邻
        Connection connection = DbUtils.getConnectionFromPool();

        // 行使获取的数据库毗邻,执行sql...........插入一条纪录到操作日志表

        // 送还毗邻给毗邻池
        DbUtils.freeConnection(connection);
    }
}

  上面的代码乍一看,似乎没啥问题,然则仔细看,实在是存在问题的!!问题出在哪儿呢?就出在从数据库毗邻池获取毗邻哪个位置。

  1.UserService会从数据库毗邻池获取一个毗邻,关闭该毗邻的自动提交;

  2.UserService然后挪用UserDao的两个接口举行数据库操作;

  3.UserDao的两个接口,都市从数据库毗邻池获取一个毗邻,然后执行sql;

  注重,第1步和第3步获得的毗邻纷歧定是同一个!!!!这才是要害。

  若是UserService和UserDao获取的数据库毗邻不是同一个,那么UserService中关闭自动提交的数据库毗邻,并不是UserDao接口中执行sql的数据库毗邻,当userService中捕捉异常,纵然执行rollback,userDao中的sql已经执行完了,并不会回滚,以是数据已经泛起纷歧致!!!

 

2.2方案1-修改接口传参

  上面的例子中,由于UserService和UserDao获取的毗邻不是同一个,以是并不能保证事务原子性;那么只要能够解决这个问题,就可以保证了

  可以修改userDao中的代码,不要每次在UserDao中从数据库毗邻池获取毗邻,而是增添一个参数,该参数就是数据库毗邻,有UserService传入,这样就能保证UserService和UserDao使用同一个数据库毗邻了

public class UserDao {

    public void deleteUserById(Connection connection, Integer id) {
        // 行使传入的数据库毗邻,执行sql...........删除用户表的一条数据
    }

    public void addOperateLog(Connection connection, Integer id, String operator) {
        // 行使传入的数据库毗邻,执行sql...........插入一条纪录到操作日志表
    }
}

  UserService挪用接口时,传入数据库毗邻,修改代码后如下:

// 事务涉及两步操作,删除用户表,增添操作日志
// 新增参数传入数据库毗邻,保证UserService和UserDao使用同一个毗邻
userDao.deleteUserById(connection, id);
userDao.addOperateLog(connection, id, operator);

  这样做,的确是能解决数据库事务的问题,然则并不推荐这样做,耦合度太高,不利于维护,修改起来也不方便;

 

2.3使用ThreadLocal解决

  ThreadLocal可以保留当前线程有用的变量,正好适合解决这个问题,而且改动的点也稀奇小,只需要在DbUtils获取毗邻的时刻,将获取到的毗邻存到ThreadLocal中即可:

public class DbUtils {

    // 使用C3P0毗邻池
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");

    // 建立threadLocal工具,保留每个线程的数据库毗邻工具
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static Connection getConnectionFromPool() throws SQLException {
        if (threadLocal.get() == null) {
            threadLocal.set(dataSource.getConnection());
        }

        return threadLocal.get();
    }

    // 省略其他方式.....
}

  然后UserService和UserDao中,恢复最初的版本,UserService和UserDao中都挪用DbUtils获取数据库毗邻,此时他们获取到的毗邻则是同一个Connection工具,就可以解决数据库事务问题了。

 

三.使用场景2——日志追踪问题

  若是理解了场景1的数据库事务问题,那么对于本小节的日志追踪,光看题目就知道是怎么回事了;

  开发历程时,会在项目中打许多的日志,一般来说,查看日志的时刻,都是通过要害字去找日志,这就需要我们在打日志的时刻明确的写入某些标识,好比用户ID、订单号、流水号...

  若是营业比较复杂,那么一个请求的处置流程就会比较长,若是将这么一长串的流程给串起来,也可以通过前面说的用户ID、订单号、流水号来串,但有个问题,某些接口没有用户ID或者订单号作为参数!!!!这个时刻,固然可以像场景1中给接口增添用户ID或者订单号作为参数,然则这样实现起来,除非想被卷铺盖,否则就别这样做。

  此时可以就可以使用ThreadLocal,封装一个工具类,提供唯一标识(可以是用户ID、订单号、或者是分布式全局ID),示例如下:

package cn.ganlixin.util;

/**
 * 形貌:
 * 日志追踪工具类,设置和获取traceId,
 * 此处的traceId使用snowFlake雪花数算法,详情可以参考:https://www.cnblogs.com/-beyond/p/12452632.html
 *
 * @author ganlixin
 * @create 2020-06-12
 */
public class TraceUtils {
    // 建立ThreadLocal静态属性,存Long类型的uuid
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    // 全局id生成器(雪花数算法)
    private static final SnowFlakeIdGenerator generator = new SnowFlakeIdGenerator(1, 1);

    public static void setUuid(String uuid) {
        // 雪花数算法
        threadLocal.set(generator.nextId());
    }

    public static Long getUuid() {
        if (threadLocal.get() == null) {
            threadLocal.set(generator.nextId());
        }
        return threadLocal.get();
    }
}

  

  使用示例:

@Slf4j
public class UserService {

    private UserDao userDao;

    public void deleteUserInfo(Integer id, String operator) {
        log.info("traceId:{}, id:{}, operator:{}", TraceUtils.getUuid(), id, operator);
        
        //.....
    }
}

@Slf4j
public class UserDao {

    public void deleteUserById(Connection connection, Integer id) {
        log.info("traceId:{}, id:{}", TraceUtils.getUuid(), id);
    }
}

  

 四.其他场景

  其他场景,实在就是行使ThreadLocal“线程私有且线程间互不影响”特征,除了上面的两个场景,常见的另有用来纪录用户的登录状态(固然也可以用session或者cookie实现)。

 

  原文地址:https://www.cnblogs.com/-beyond/p/13111015.html 

,

Allbet代理

欢迎进入Allbet代理(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

Sunbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧博亚洲app下载:ThreadLocal的使用场景剖析

网友评论

  • (*)

最新评论

  • 联博 2020-07-08 00:16:51 回复

    欧博亚洲官方注册欢迎进入欧博亚洲官方注册(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。今日签到

    1

文章归档

站点信息

  • 文章总数:642
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1010
  • 评论总数:267
  • 浏览总数:7544