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

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

相关推荐
心灵宝贝3 分钟前
Mac用户安装JDK 22完整流程(Intel版dmg文件安装指南附安装包下载)
java·开发语言·macos
ta是个码农5 分钟前
Mysql——日志
java·数据库·mysql·日志
今***b18 分钟前
Python 操作 PPT 文件:从新手到高手的实战指南
java·python·powerpoint
骑驴看星星a20 分钟前
Vue中的scoped属性
前端·javascript·vue.js
David爱编程20 分钟前
volatile 关键字详解:轻量级同步工具的边界与误区
java·后端
四月_h27 分钟前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_4275060832 分钟前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。2 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
lecepin2 小时前
AI Coding 资讯 2025.8.27
前端·ai编程
TimelessHaze3 小时前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae