Swing JTextArea多线程问题--InterruptedException

时间:2010-12-22 08:58:35

标签: java multithreading swing

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

因此,我使用JTextArea为我的日志创建了一个swing 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的新手,所以我无法理解我在这里做错了什么?

提前致谢。

4 个答案:

答案 0 :(得分:5)

你不想直接从另一个线程操纵Swing对象,你想要post manipulations to it's event queue

答案 1 :(得分:3)

您不应该从其他线程更新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);

    }

答案 2 :(得分:2)

Max,你没有提到,你是interrupt线程。但你肯定做到了。所以你的问题实际上包含两个不同的问题。

  

append有时会抛出InterruptedException

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

其他人对于将所有内容放入EDT主题并不完全正确。 JTextArea.append方法是线程安全的,因此无需包装。您调用的唯一方法(工作线程)是setCaretPosition。那你为什么接受invokeLater答案呢?可能是因为将文档访问放在一个线程中会删除所有锁定问题。请参阅AbstractDocument.writeLock open jdk code,其中解释了这一点Error

因此看起来在EDT线程中放置Document写入是非常必要的,但只有在想要中断线程时才会这样。作为非常不友好的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
          }
        }
    });
  }
}

答案 3 :(得分:0)

我知道这是一篇老文章,但是如果对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;
    }
  }

如果滚动窗格不可用,则使用脱字符号方法,可以在其中指定节流以最大程度地减少摆动代码内死锁的风险。