从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
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/falselog.info("---------> instance == null :{}", instance == null);if (instance == null) {// 类级别的同步锁,保证多线程下仅初始化一次synchronized (SFTPUtil.class) {if (instance == null) {// 创建 SFTPUtil 实例instance = new SFTPUtil();// 获取 SFTP 连接,并赋值给静态变量 sftpsftp = 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. 实例创建逻辑:是否每次返回新实例?
不会每次返回新实例,因为代码用了 单例模式(双重检查锁定),核心逻辑:
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
方法时,都会执行:
SFTPUtil sf = SFTPUtil.getInstance(...);
但由于 getInstance
是单例逻辑,只有第一次调用 会触发 new SFTPUtil()
和 sftp = instance.connect(...)
;后续调用直接返回已存在的 instance
。
潜在问题与优化建议
(1)单例与多环境冲突
如果 getInstance
的入参(host
、port
等)在不同调用中变化,但实例已创建,会导致后续调用使用第一次的参数(比如第一次用 host=192.168.1.1
创建实例,后续传入 host=10.0.0.1
也不会重新创建实例)。
风险:若业务需要连接不同 SFTP 服务器,单例模式会导致参数失效。
解决方案:
- 放弃单例,改为每次创建新实例(去掉
instance
静态变量,直接new SFTPUtil()
); - 或根据入参(
host
、port
等)维护多个单例(用Map
缓存不同参数对应的实例)。
(2)sftp
静态变量的线程安全
private static ChannelSftp sftp;
是静态变量,若多线程同时使用 sftp
执行操作(如 cd
、get
),会引发线程安全问题(不同线程操作同一个 ChannelSftp
可能导致命令混乱)。
解决方案:
- 将
sftp
改为实例变量(非静态),每个SFTPUtil
实例持有独立的sftp
通道; - 或对
sftp
的操作加锁(synchronized
或使用线程安全的工具类)。
(3)连接未释放的风险
若 SFTPUtil
是单例且长期运行,sftp
通道可能因超时、网络波动断开,且没有重连逻辑,会导致后续操作失败。
解决方案:
- 添加连接校验与重连逻辑(在
download
前检查sftp.isConnected()
,断开则重新connect
); - 实现
close
方法,在业务结束时主动释放连接。
总结
- 是否每次返回新实例:否,全局仅 1 个实例(单例模式)。
- 锁的作用:保证多线程下仅创建 1 个实例,线程安全。
- 潜在问题:单例与多参数冲突、
sftp
静态变量线程不安全、连接未释放。
若业务需要连接不同 SFTP 服务器,或多线程操作 sftp
,建议调整单例逻辑或增加线程安全处理。