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

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 ,建议调整单例逻辑或增加线程安全处理。

相关推荐
Asort1 分钟前
JavaScript 从零开始(六):控制流语句详解——让代码拥有决策与重复能力
前端·javascript
无双_Joney20 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥22 分钟前
前端必学的 CSS Grid 布局体系
前端·css
ccnocare23 分钟前
选择文件夹路径
前端
艾小码23 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月24 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁28 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅28 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸29 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端