
JSch 的 SftpProgressMonitor.count() 虽在 EDT 中执行,但因 EDT 被阻塞而无法及时重绘 UI;根本原因在于 Swing 的 repaint 请求需等待 EDT 空闲才能处理,而同步 SFTP 操作长期占用 EDT 导致界面“假死”。
jsch 的 `sftpprogressmonitor.count()` 虽在 edt 中执行,但因 edt 被阻塞而无法及时重绘 ui;根本原因在于 swing 的 repaint 请求需等待 edt 空闲才能处理,而同步 sftp 操作长期占用 edt 导致界面“假死”。
Swing 是单线程架构,所有 UI 更新(如 setValueAt()、repaint())必须在事件分派线程(EDT)中执行,且仅当 EDT 处于空闲状态时,系统才会批量处理挂起的 repaint 请求。在您的代码中,SftpProgressMonitor 的 count() 方法确实运行在 EDT 上(因为 JSch 默认在调用线程中回调),但问题在于:SFTP 上传/下载操作本身是同步阻塞的,它会持续占用 EDT,导致 repaint 请求始终无法被调度执行——直到整个传输完成、EDT 恢复空闲,所有累积的 UI 更新才一次性刷新,表现为“进度条跳变到 100%”。
✅ 正确做法是将 SFTP 操作移出 EDT,同时确保 UI 更新仍安全地回到 EDT:
public class MySftpProgressMonitor implements SftpProgressMonitor {
private final JTable table;
private final DefaultTableModel tableModel;
private final int rowIndex;
public MySftpProgressMonitor(JTable table, DefaultTableModel model, String src) {
this.table = table;
this.tableModel = model;
this.rowIndex = model.getRowCount();
// 初始化行(注意:init() 也在 EDT 中调用,安全)
model.addRow(new Object[]{src, 0});
}
@Override
public boolean count(long bytes) {
// ✅ 计算进度(纯计算,无 UI 操作)
int percentage = computePercentage(bytes);
// ✅ 异步安全更新 UI:使用 SwingUtilities.invokeLater
SwingUtilities.invokeLater(() -> {
tableModel.setValueAt(percentage, rowIndex, 1);
// 可选:强制触发视图刷新(通常 setValueAt + fireTableCellUpdated 已足够)
table.repaint();
});
return true;
}
@Override
public void init(int op, String src, String dest, long maxBytes) {
// 已在构造中处理,此处可留空或做日志
}
@Override
public void end() {
SwingUtilities.invokeLater(() -> {
tableModel.setValueAt("Done", rowIndex, 1);
});
}
private int computePercentage(long bytes) {
// 示例:假设已知总大小 maxBytes(需在 init 中保存)
// 实际中建议将 maxBytes 存为成员变量
return (int) Math.min(100, bytes * 100 / Math.max(1, 10_000_000L));
}
}
⚠️ 关键注意事项:
-
绝不可在 EDT 中执行耗时 I/O 操作:调用
channelSftp.put(...)或get(...)时,务必在SwingWorker、ExecutorService或独立线程中执行; -
SftpProgressMonitor回调线程取决于 JSch 调用方线程——若从 EDT 启动 SFTP,则count()也在 EDT;因此必须用invokeLater包裹 UI 更新; - 推荐完整方案:使用
SwingWorker<void integer></void>封装 SFTP 任务,在doInBackground()中执行传输并publish(percentage),在process()中更新表格(天然在 EDT); -
table.setValueAt()不会自动触发视图重绘?请确认DefaultTableModel已正确注册监听器(标准 Swing 组件默认支持),否则需手动调用tableModel.fireTableCellUpdated(row, col)。
总结:UI 冻结不是线程错位,而是 EDT 被阻塞导致 repaint 队列积压。解法核心是「I/O 移出 EDT + UI 更新回归 EDT」,二者缺一不可。











