本文目录导读:

这是一个关于 JEditorPane + HTMLEditorKit 在解析 HTML 时出现死锁的经典问题,这个 Bug 在 Java 6/7/8 中都比较常见,尤其是在多线程环境下加载或设置 HTML 内容时。
问题根源
死锁通常发生在以下场景:
Thread 1: 持有 A 锁,等待 B 锁
Thread 2: 持有 B 锁,等待 A 锁
在 HTMLEditorKit 的 HTML 解析器中,具体表现为:
- Swing 事件分发线程 (EDT):持有某个解析器相关的锁
- 后台加载线程:也试图获取同一个锁,但可能已经在另一个锁上阻塞
典型死锁场景
// 在非 EDT 线程中修改 HTML
new Thread(() -> {
// 可能触发死锁
editorPane.setText("<html>...</html>");
}).start();
或者在 EDT 中:
SwingUtilities.invokeLater(() -> {
// 如果这个操作触发了复杂的 HTML 解析...
editorPane.setText(complexHtmlContent);
});
解决方案
始终在 EDT 中操作 JEditorPane
SwingUtilities.invokeLater(() -> {
editorPane.setText(htmlContent);
});
使用 DocumentFilter 或自定义 Document
// 使用 Document 直接操作,避免 HTML 解析器的锁竞争
String html = "<html><body>Content</body></html>";
SwingUtilities.invokeLater(() -> {
try {
HTMLEditorKit kit = (HTMLEditorKit) editorPane.getEditorKit();
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
kit.read(new StringReader(html), doc, 0);
editorPane.setDocument(doc);
} catch (Exception e) {
e.printStackTrace();
}
});
使用 SwingWorker 处理复杂加载
class HtmlLoader extends SwingWorker<Void, Void> {
private final String url;
private final JEditorPane editorPane;
HtmlLoader(String url, JEditorPane editorPane) {
this.url = url;
this.editorPane = editorPane;
}
@Override
protected Void doInBackground() throws Exception {
// 在后台线程中下载内容
String html = downloadHtml(url);
// 在 EDT 中更新 UI
SwingUtilities.invokeLater(() -> {
editorPane.setText(html);
});
return null;
}
}
避免在非 EDT 线程中直接调用 setText
如果必须在后台线程中准备 HTML,使用 StringBuilder 构建完成后,再通过 invokeLater 设置:
// 后台线程
String html = buildHtmlContent();
SwingUtilities.invokeLater(() -> {
editorPane.setText(html);
});
考虑使用替代组件
如果死锁问题持续出现且严重影响应用,考虑使用:
- JavaFX WebView:对于复杂 HTML 渲染更稳定
- JTextPane +自定义渲染:对于简单 HTML
- 第三方库:如
Flying Saucer(XHTML renderer)
调试死锁
如果怀疑发生死锁,可以通过 jstack 或 Thread Dump 来分析:
# 获取 Java 进程的线程转储 jstack <pid> > thread_dump.txt
在 dump 中查找类似的阻塞情况:
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f...
java.lang.Thread.State: BLOCKED
at javax.swing.text.html.HTMLEditorKit.parse
- waiting to lock <0x000000076b...>
- locked <0x000000076c...>
"AWT-EventQueue-0" #11 prio=6 os_prio=0 tid=0x00007f...
java.lang.Thread.State: BLOCKED
at javax.swing.text.html.HTMLEditorKit.parse
- waiting to lock <0x000000076c...>
- locked <0x000000076b...>
预防措施
- 只使用 EDT 线程更新任何 Swing 组件
- 避免在事件监听器中执行耗时操作
- 使用
SwingUtilities.invokeLater包装所有 UI 更新 - 考虑线程安全的文档操作,特别是涉及 HTML 解析时
完整示例
import javax.swing.*;
import javax.swing.text.html.*;
import java.io.*;
public class SafeHtmlEditor extends JPanel {
private JEditorPane editorPane;
public SafeHtmlEditor() {
editorPane = new JEditorPane();
editorPane.setEditorKit(new HTMLEditorKit());
editorPane.setEditable(false);
add(new JScrollPane(editorPane));
}
public void safeSetHtml(String html) {
// 始终在 EDT 中操作
if (SwingUtilities.isEventDispatchThread()) {
doSetHtml(html);
} else {
SwingUtilities.invokeLater(() -> doSetHtml(html));
}
}
private void doSetHtml(String html) {
try {
HTMLEditorKit kit = (HTMLEditorKit) editorPane.getEditorKit();
HTMLDocument doc = new HTMLDocument();
kit.read(new StringReader(html), doc, 0);
editorPane.setDocument(doc);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
遵循这些建议,应该能有效避免 JEditorPane + HTMLEditorKit 的死锁问题,如果问题仍然存在,可能需要考虑更换渲染技术或使用更现代的 UI 框架。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播