背景&问题
项目中使用java swing做了个多线程处理任务的界面,在界面上显示多线程任务的log信息,为了实时显示log信息,使用了log4j的org.apache.log4j.WriterAppender并单独开了线程。但是log信息只在多线程任务结束后才一次显示。
解决方案
SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。EventQueue的派发机制由单独的一个线程管理,这个线程称为事件派发线程(EDT)。和其他很多桌面API一样,Swing将GUI请求放入一个事件队列中执行。 通过EDT,使得不具备线程安全的Swing函数库避开了并发访问的问题。
因此,任何涉及多线程任务的UI,更新操作要放在主线程,将耗时的业务逻辑另起线程执行,保持主线程不被耗时任务中断即可。
package pdf.page;
...
import pdf.page.log.Log4JGUIAppenderThread;
public class MakePageGUI implements ActionListener, ChangeListener {
static Logger logger = Logger.getLogger(MakePageGUI.class);
static final long LOG_RESTART_SIZE = 10000;
JFrame frame;
...
MakePage page;
MakePageGUI() {
page = new MakePage();
}
public static void main(String[] args) {
new MakePageGUI().show();
}
public void show() {
// 创建 JFrame 实例
frame = new JFrame("档案分页工具");
...
/*
* 调用用户定义的方法并添加组件到面板
*/
placeComponents();
// 设置界面可见
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
page.shutdownNow();
frame.dispose();
}
});
}
/**
* 监听的方法
*/
public void actionPerformed(ActionEvent e) {
...
case "tifPage":
if (null == this.input || null == this.config || null == this.output) {
JOptionPane.showMessageDialog(null, "请选择档案文件或目录", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
beforePage();
// 耗时任务单独起线程执行,不中断UI的主线程
new Thread(() -> page.page(input, config, output, MakePage.PAGE_TYPE_TIF)).start();
break;
case "timer":
if (page.isDone()) {
afterPage();
}
progressBar.setValue(page.getProgress());
timePass.setText(formatTime(page.getTimePass()));
break;
}
}
// 进度条
public void stateChanged(ChangeEvent e) {
if (e.getSource() == progressBar) {
progressLabel.setText(page.getCompleted() + "/" + page.getVolumeSize());
}
}
}