从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑

java 复制代码
public class SFTPUtil {

    // 16 usages(注释为截图中的使用统计,实际代码无需保留)
    private static ChannelSftp sftp;

    // 6 usages(注释为截图中的使用统计,实际代码无需保留)
    private volatile static SFTPUtil instance = null;

    // 1 usage(注释为截图中的使用统计,实际代码无需保留),私有构造方法,防止外部直接实例化
    private SFTPUtil() {
    }

    public static SFTPUtil getInstance(String host, int port, String username, String password) {
        // 日志打印 instance 是否为 null,{} 是占位符,实际会替换为 true/false
        log.info("---------> instance == null :{}", instance == null);
        if (instance == null) {
            // 类级别的同步锁,保证多线程下仅初始化一次
            synchronized (SFTPUtil.class) {
                if (instance == null) {
                    // 创建 SFTPUtil 实例
                    instance = new SFTPUtil();
                    // 获取 SFTP 连接,并赋值给静态变量 sftp
                    sftp = instance.connect(host, port, username, password);
                }
            }
        }
        return instance;
    }

    // 截图中未展示 connect 方法的具体实现,需补充(根据上下文推测是建立 ChannelSftp 连接的逻辑)
    private ChannelSftp connect(String host, int port, String username, String password) {
        // 这里需补充实际连接 SFTP 服务器的代码,示例如下(基于 JSch 库):
        ChannelSftp channelSftp = null;
        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(username, host, port);
            session.setPassword(password);
            // 跳过严格主机密钥检查(测试环境可用,生产环境需配置 known_hosts)
            session.setConfig("StrictHostKeyChecking", "no"); 
            session.connect();
            
            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;
        } catch (JSchException e) {
            // 实际需完善异常处理,比如日志记录或抛出自定义异常
            log.error("SFTP 连接失败", e);
        }
        return channelSftp;
    }

    // 若有其他工具方法(如下载、上传等),需补充...
    public downLoadZipMethod(){
    // 执行下载 ".ok" 文件
SFTPUtil sf = SFTPUtil.getInstance(
    props.getSftpHost(), 
    Integer.parseInt(props.getSftpPort()), 
    props.getSftpUsername(), 
    props.getSftpPassword()
);
}
}

这块代码是什么时候会调用会每次调用downloadZipFile都会调用这个SFTPUtil.getInstance(),走到第二个图的逻辑,第二个图中每一次都会返回一个新的SFTPUtil实例吗?第二个图中所加的锁是什么作用?

以下从 实例创建逻辑锁的作用是否每次返回新实例 三个维度详细分析:

1. 实例创建逻辑:是否每次返回新实例?

不会每次返回新实例 ,因为代码用了 单例模式(双重检查锁定),核心逻辑:

java 复制代码
private volatile static SFTPUtil instance = null;

public static SFTPUtil getInstance(...) {
    if (instance == null) { // 第一次检查(无锁,提升性能)
        synchronized (SFTPUtil.class) { // 加类锁,保证线程安全
            if (instance == null) { // 第二次检查(避免多线程重复创建)
                instance = new SFTPUtil(); // 仅第一次调用时创建实例
                sftp = instance.connect(...); 
            }
        }
    }
    return instance; // 后续调用直接返回已创建的实例
}
  • 结论 :无论调用 getInstance 多少次,全局仅会创建 1 个 SFTPUtil 实例(单例特性)。

2. 锁的作用:synchronized (SFTPUtil.class)

synchronized (SFTPUtil.class)类级别的锁,作用是:

  • 保证线程安全 :在多线程环境下,避免多个线程同时进入 if (instance == null) 逻辑,导致重复创建 SFTPUtil 实例。
  • 实现单例的核心 :通过双重检查(if (instance == null) 两次判断) + 类锁,确保 instance = new SFTPUtil() 仅执行一次。

3. downloadZipFile 调用 getInstance 的时机

每次调用 downloadZipFile 方法时,都会执行:

java 复制代码
SFTPUtil sf = SFTPUtil.getInstance(...);

但由于 getInstance 是单例逻辑,只有第一次调用 会触发 new SFTPUtil()sftp = instance.connect(...);后续调用直接返回已存在的 instance

潜在问题与优化建议

(1)单例与多环境冲突

如果 getInstance 的入参(hostport 等)在不同调用中变化 ,但实例已创建,会导致后续调用使用第一次的参数 (比如第一次用 host=192.168.1.1 创建实例,后续传入 host=10.0.0.1 也不会重新创建实例)。

风险:若业务需要连接不同 SFTP 服务器,单例模式会导致参数失效。

解决方案

  • 放弃单例,改为每次创建新实例 (去掉 instance 静态变量,直接 new SFTPUtil() );
  • 或根据入参(hostport 等)维护多个单例 (用 Map 缓存不同参数对应的实例)。
(2)sftp 静态变量的线程安全

private static ChannelSftp sftp; 是静态变量,若多线程同时使用 sftp 执行操作(如 cdget ),会引发线程安全问题 (不同线程操作同一个 ChannelSftp 可能导致命令混乱)。

解决方案

  • sftp 改为实例变量 (非静态),每个 SFTPUtil 实例持有独立的 sftp 通道;
  • 或对 sftp 的操作加锁(synchronized 或使用线程安全的工具类)。
(3)连接未释放的风险

SFTPUtil 是单例且长期运行,sftp 通道可能因超时、网络波动断开,且没有重连逻辑,会导致后续操作失败。

解决方案

  • 添加连接校验与重连逻辑 (在 download 前检查 sftp.isConnected() ,断开则重新 connect );
  • 实现 close 方法,在业务结束时主动释放连接。

总结

  • 是否每次返回新实例:否,全局仅 1 个实例(单例模式)。
  • 锁的作用:保证多线程下仅创建 1 个实例,线程安全。
  • 潜在问题 :单例与多参数冲突、sftp 静态变量线程不安全、连接未释放。

若业务需要连接不同 SFTP 服务器,或多线程操作 sftp ,建议调整单例逻辑或增加线程安全处理。

相关推荐
狂奔小菜鸡4 分钟前
Day18 | 深入理解Object类
java·后端·java ee
jiayong235 分钟前
Maven NUL文件问题 - 解决方案实施报告
java·maven
未秃头的程序猿6 分钟前
🔒 从单机到分布式:三大锁机制深度剖析与实战指南
java·后端
大猫子的技术日记8 分钟前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
kungggyoyoyo9 分钟前
TRAE中国版SOLO模式上线!我用它从0到1开发了一款AI小说编辑器
前端·vue.js·trae
ohyeah10 分钟前
栈:那个“先进后出”的小可爱,其实超好用!
前端·数据结构
心随雨下20 分钟前
typescript中Triple-Slash Directives如何使用
前端·javascript·typescript
s***353024 分钟前
Spring Boot3.x集成Flowable7.x(一)Spring Boot集成与设计、部署、发起、完成简单流程
java·spring boot·后端
自在极意功。25 分钟前
AJAX 深度详解:从基础原理到项目实战
前端·ajax·okhttp
s***45326 分钟前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端