JEditorPaneHTMLEditorKitParserDeadlock死锁

wen java案例 1

本文目录导读:

JEditorPaneHTMLEditorKitParserDeadlock死锁

  1. 问题根源
  2. 典型死锁场景
  3. 解决方案
  4. 调试死锁
  5. 预防措施
  6. 完整示例

这是一个关于 JEditorPane + HTMLEditorKit 在解析 HTML 时出现死锁的经典问题,这个 Bug 在 Java 6/7/8 中都比较常见,尤其是在多线程环境下加载或设置 HTML 内容时。

问题根源

死锁通常发生在以下场景:

Thread 1: 持有 A 锁,等待 B 锁
Thread 2: 持有 B 锁,等待 A 锁

HTMLEditorKit 的 HTML 解析器中,具体表现为:

  1. Swing 事件分发线程 (EDT):持有某个解析器相关的锁
  2. 后台加载线程:也试图获取同一个锁,但可能已经在另一个锁上阻塞

典型死锁场景

// 在非 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...>

预防措施

  1. 只使用 EDT 线程更新任何 Swing 组件
  2. 避免在事件监听器中执行耗时操作
  3. 使用 SwingUtilities.invokeLater 包装所有 UI 更新
  4. 考虑线程安全的文档操作,特别是涉及 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 框架。

抱歉,评论功能暂时关闭!