【问题标题】:Swing JTextArea multithreading problem - InterruptedExceptionSwing JTextArea 多线程问题 - InterruptedException
【发布时间】:2011-05-29 07:10:25
【问题描述】:

我有一个简单的控制台应用程序,它在多个线程(其中 10-20 个)中运行计算。现在我正在尝试创建一个简单的 GUI,它允许我选择要处理的文件并打印来自所有线程的日志。

所以,我为我的日志创建了一个带有 JTextArea 的摇摆 GUI 和一个将信息记录到日志的方法:

public synchronized void log(String text) {
    logArea.append(text);
    logArea.append("\n");

    if (logArea.getDocument().getLength() > 50000) {
        try {
            logArea.getDocument().remove(0,5000);
        } catch (BadLocationException e) {
            log.error("Can't clean log", e);
        }
    }

    logArea.setCaretPosition(logArea.getDocument().getLength());
}

但是,setCaretPosition 方法有时会在等待某个锁时死锁,而 append 有时会抛出 InterruptedException。

Exception in thread "Thread-401" java.lang.Error: Interrupted attempt to aquire write lock
at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1334)
at javax.swing.text.AbstractDocument.insertString(AbstractDocument.java:687)
at javax.swing.text.PlainDocument.insertString(PlainDocument.java:114)
at javax.swing.JTextArea.append(JTextArea.java:470)
at lt.quarko.aquila.scripts.ui.ScriptSessionFrame.log(ScriptSessionFrame.java:215)

我是 Swing 的新手,所以我不明白我在这里做错了什么?

提前致谢。

【问题讨论】:

    标签: java multithreading swing


    【解决方案1】:

    你不想直接从另一个线程操作 Swing 对象,你想post manipulations to its event queue

    【讨论】:

    • 这样在处理高频更新时仍然没有解决死锁问题。请参阅下面的解决方案。
    【解决方案2】:

    我知道这是一篇旧文章,但是如果 JTextArea 的更新频率非常高,使用 setCaretPosition() 方法将视口自动滚动到底部可能会导致死锁问题。仅在 EDT 上进行更新并不能避免问题或提供任何改进。

    避免问题的方法:

    • 不要尝试自动滚动,并将插入符号更新策略设置为 NEVER_UPDATE。可能不太理想,但在我们的主应用程序中,我们具有“滚动锁定”功能,因此可以在仍在捕获文本的同时查看输出。
    • 限制 setCaretPosition() 的使用:如果处理大量更新,则在 X 次更新后设置插入符。我有我们的应用程序按行跟踪诊断,所以我可以每次都推迟插入符号更新,直到捕获 X 行。请注意,如果受到限制,您仍然需要将插入符号更新策略设置为 NEVER_UPDATE。
    • 如果您的 JTextArea 在 JScrollPane 中,则直接使用垂直滚动条并在每次更新文本区域后将其重新定位到最大位置。确保在重新定位滚动条时使用 invokeLater(),以便考虑到由于添加文本而发生的任何大小变化。

    以下是我创建的用于将文本捕获到 JTextComponent(或其子类)中的类的代码片段,用于检查滚动窗格方法是否有效:

      if (autoScroll && (scrollPane != null)) {
        final JScrollBar vertical = scrollPane.getVerticalScrollBar();
        if (vertical != null) {
          appendText(doc, txt);
          java.awt.EventQueue.invokeLater(new Runnable() {
            @Override public void run() {
              vertical.setValue(vertical.getMaximum());
            }
          });
          return;
        }
      }
    

    如果滚动窗格不可用,则使用插入符号方法,其中可以指定限制以最大程度地减少摇摆代码内部死锁的风险。

    【讨论】:

      【解决方案3】:

      Max,你没有提到,你 interrupt 线程。但你肯定做到了。所以你的问题实际上由 2 个独立的问题组成。

      append 有时会抛出 InterruptedException

      我刚刚陷入同样的​​境地,不知道如何处理。当我中断线程时,Document.insertString 无法抛出这种错误。

      其他人不太正确地将所有内容都放在 EDT 线程中。 JTextArea.append 方法是线程安全的,所以不需要包装。您不应该(在工作线程中)调用的唯一方法是setCaretPosition。那么为什么你接受invokeLater 的答案呢?可能是因为将文档访问放在一个线程中消除了所有锁定问题。见AbstractDocument.writeLockopen jdk code,这解释了一点Error

      所以看起来将Document 写入 EDT 线程确实是必要的,但只有当人们想要中断线程时。作为一种非常不友好的AbstractDocument 行为的解决方法,在这种情况下会抛出Error

      我为Document Error 提出了以下解决方法。它不是很干净,因为在设置bInterrupted 标志后,线程可能会不幸中断。但可以通过以受控、同步的方式执行Thread.interrupt() 来避免这种情况。

      // test the flag and clear it (interrupted() method does clear it)
      boolean bInterrupted = Thread.interrupted();
      m_doc.insertString(m_doc.getLength(), s, null);
      // restore the original interrupted state
      if (bInterrupted)
        Thread.currentThread().interrupt();
      

      setCaretPosition 方法有时在等待某个锁时会死锁

      这是我的插入符号更新解决方案。我可以简单地使用invokeLater,但我想避免多余的调用,所以我添加了一个额外的标志:

      /** <code>true</code> when gui update scheduled. This flag is to avoid
        * multiple overlapping updates, not to call
        * <code>invokeLater</code> too frequently.
       */
      private volatile boolean m_bUpdScheduled;
      
      /** Updates output window so that the last line be visible */
      protected void update()
      {
        if (!m_bUpdScheduled) {
          m_bUpdScheduled = true;
          EventQueue.invokeLater(new Runnable() {
              public void run() {
                m_bUpdScheduled = false;
                try {
                  m_ebOut.setCaretPosition(m_doc.getLength());
                }
                catch (IllegalArgumentException iae) {
                  // doc not in sync with text field - too bad
                }
              }
          });
        }
      }
      

      【讨论】:

      • JTextArea.append 方法是线程安全的 重复了一个神话,即使是摇摆开发者也喜欢 :-) 这不是真的 - 请参阅最新的 jdk 7 api doc - 从来没有(虽然争论很多而且很激烈)。
      • @kleopatra,感谢您指出这一点。但是AbstractDocument.insertString 在 jdk 6 和 7 中都是线程安全的。有关详细信息,请参阅我的回答 there
      • 仅供参考,您仍然会因高频更新而陷入僵局。请参阅下面的解决方案,了解如何避免或减轻死锁问题。
      【解决方案4】:

      你不应该从其他线程更新 ui 组件,你应该使用 EventDispatchThread。这是解决方案;

       public synchronized void log(String text) {
              Runnable  runnable = new Runnable() {
                  public void run(){
                      logArea.append(text);
                      logArea.append("\n");
                      if (logArea.getDocument().getLength() > 50000) {
                          try {
                              logArea.getDocument().remove(0, 5000);
                          } catch (BadLocationException e) {
                              log.error("Can't clean log", e);
                          }
                      }
                      logArea.setCaretPosition(logArea.getDocument().getLength());
                  }
              }
              SwingUtilities.invokeLater(runnable);
      
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-24
        • 2014-01-03
        • 2010-09-26
        相关资源
        最近更新 更多