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