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

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

相关推荐
偷光9 分钟前
浏览器中的隐藏IDE: Elements (元素) 面板
开发语言·前端·ide·php
DKPT13 分钟前
JVM栈溢出和堆溢出哪个先满?
java·开发语言·jvm·笔记·学习
m0_4750645013 分钟前
jvm双亲委派的含义
java·jvm
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 爱心捐赠网站为例,包含答辩的问题和答案
java·eclipse
江拥羡橙5 小时前
Vue和React怎么选?全面比对
前端·vue.js·react.js
楼田莉子6 小时前
Qt开发学习——QtCreator深度介绍/程序运行/开发规范/对象树
开发语言·前端·c++·qt·学习
暮之沧蓝7 小时前
Vue总结
前端·javascript·vue.js
尘觉7 小时前
中秋节与 Spring Boot 的思考:一场开箱即用的团圆盛宴
java·spring boot·后端
木易 士心7 小时前
Promise深度解析:前端异步编程的核心
前端·javascript
Le1Yu7 小时前
2025-10-7学习笔记
java·笔记·学习