3

我有一些代码进行一些初始化(包括创建一个JTextArea对象),启动三个单独的线程,然后这些线程尝试更新JTextArea(即append()它),但它根本不起作用。什么都没有显示JTextArea(但是,在初始化期间,我在上面打印了一些测试行,效果很好)。这是怎么回事?我怎样才能解决这个问题?此外,这些线程中的每一个在每次必须更新JTextArea.

抱歉,我没有提供任何代码,它们都分布在几个文件中。

4

5 回答 5

4

虽然我相信 API 已经声明 JTextArea#append(...) 是线程安全的,但我听说过它存在问题,并建议仅在 EDT 上调用它。典型的例子是使用 SwingWorker 并通过调用 publish.append 到 process 方法中的 JTextArea。

对我来说,尽管没有代码,但很难向您提出任何具体建议。我确实想知道您是否将 EDT 置于代码中的某个位置。

编辑:根据您的评论查看本教程:Swing 中的并发


编辑 2:根据Tim Perry的评论,线程安全性的丧失及其背后的原因已发布在此 Java 错误中,这与将文本添加到 JTextArea 文档的这行代码有关:

doc.insertString(doc.getLength(), str, null);

该行分解为两行:

  1. int len=doc.getLength();
  2. doc.insertString(len,str,null);

问题是,如果 Document、doc 在第 1 行和第 2 行之间发生变化,尤其是 Document 长度,则可能会出现问题。

于 2011-02-01T23:12:48.700 回答
3

在 Java 1.6 中,文档JTextArea.append说:

将给定的文本附加到文档的末尾。如果模型为 null 或字符串为 null 或为空,则不执行任何操作。

此方法是线程安全的,尽管大多数 Swing 方法不是。请参阅如何使用线程了解更多信息。

在 JDK7 中缺少第二部分:

将给定的文本附加到文档的末尾。如果模型为 null 或字符串为 null 或为空,则不执行任何操作。

如果您查看Document接口(JTextArea可以使用用户提供的实例),即使实现是线程安全的,也无法以线程安全的方式附加文本。摆动线程刚刚坏了。我强烈建议在 Swing 组件附近的任何地方都严格遵守 AWT EDT。

于 2011-02-02T11:37:57.167 回答
2

JTextArea.append(..)是线程安全的,所以从不同的线程调用它应该是安全的。

但是状态的javadoc .append()

Does nothing if the model is null or the string is null or empty.

因此,请确保 JTextArea 的模型已初始化(通过适当的构造函数)。

于 2011-02-01T23:23:29.130 回答
2

I trust experienced guys who warn about believing in Document's thread safety. However it's hard to believe that an application exploits this problem so easily resulting in JTextArea displaying nothing at all. Maybe some other methods are used except append and they cause general failure. I attach a testing app, that I run on Debian with Oracle jre 6 (and also Win7 with java 6 64bit) and see no problems.

During the development process I had to fix several mistakes which included:

  1. Not synchronizing getLength() and insertString() method which resulted in wrong insertion placement and even BadLocationExceptions. Other threads were modifying the document between these 2 instructions. Even if they were on the same line :)
  2. Swallowing BadLocationException. I was sure it's impossible to hit it, but was wrong.

After realizing the above, especially the need of creating a critical section for getLength() and insertString() pair, it's obvious that JTextArea would fail (see Tom Hawtin's answer here). And I saw it actually did, because not every insertString was executed successfully and the resulting text was shorter than it should be. However this problem did not occur with loop count 10000, only at 100000. And looking into jdk 7 code of JTextArea.append, which does nothing but modify the underlying document, it seems that synchronizing JTextArea externally would also do.

Inserting at 0 also worked well, without any synchronization, although it took ages to complete.

Usually in such applications one wants to scroll to the last line. Hey, this is awt. You can't setCaretPosition out of EDT. So I don't. Scroll manually. My suggestion to solve scrolling is in another answer.

If you guys see problems with this application on your system, please comment. My conclusion is that Document.insertString is thread safe, however to use it effectively, together with getLength, synchronization is neccessary.

In the following code PlainDocument is subclassed to create synchronized append method, which wraps getLength and insertString into a lock. This lock has protected access, so I couldn't use it without a separate class. However external synchronization also gave correct results.

BTW: Sorry for so many edits. Finally I restructured this answer after learning more.

The code:

import java.awt.*;
import java.util.concurrent.CountDownLatch;
import javax.swing.*;
import javax.swing.text.*;

class SafePlainDocument extends PlainDocument
{
  public void append(String s)
  {
    writeLock();
    try {
      insertString(getLength(), s,  null);
    }
    catch (BadLocationException e) {
      e.printStackTrace();
    }
    finally
    {
      writeUnlock();
    }
  }
}

public class StressJText
{
  public static CountDownLatch m_latch;
  public static SafePlainDocument m_doc;
  public static JTextArea m_ta;

  static class MyThread extends Thread
  {
    SafePlainDocument m_doc;
    JTextArea m_ta;

    public MyThread(SafePlainDocument doc)
    {
      m_doc = doc;
    }

    public void run() 
    {
      for (int i=1; i<=100000; i++) {
        String s = String.format("%19s %9d\n", getName(), i);
        m_doc.append(s);
      }
      StressJText.m_latch.countDown();
    }
  }

  public static void main(String sArgs[])
  {
    System.out.println("hello");
    final int cThreads = 5;
    m_latch = new CountDownLatch(cThreads);
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
          JFrame frame = new JFrame();
          m_ta = new JTextArea();
          m_doc = new SafePlainDocument();
          m_ta.setDocument(m_doc);
          m_ta.setColumns(50);
          m_ta.setRows(20);
          JScrollPane scrollPane = new javax.swing.JScrollPane();
          scrollPane.setViewportView(m_ta);
          frame.add(scrollPane);
          frame.pack();
          frame.setVisible(true);

          for (int it=1; it<=cThreads; it++) {
            MyThread t = new MyThread(m_doc);
            t.start();
          }
        }
    });
    try {
      m_latch.await();
    }
    catch (InterruptedException ie) {
      ie.printStackTrace();
    }
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
          System.out.println("tf len: " + m_ta.getText().length());
          System.out.println("doc len: " + m_doc.getLength());
          System.exit(0);
        }
    });
  }
}
于 2012-10-07T13:34:56.900 回答
2

JTextArea thread safe?

Definitely not. Not in general. As others stated even the append method is no longer documented to be thread safe. However Java 7 documentaion of AbstractDocument.insertString clearly states that this method is thread safe.

Using AbstractDocument.insertString seems to be safe according to docs. And that's the only reasonable option. Updating string model in gui thread would be a drastic performance loss.

How about JTextArea.append? I assume it depends on the underlying Document. For PlainDocument and DefaultStyledDocument it could be thread safe. For other models one should inspect the related documentation. If one does not know what is the underlying document, then they should treat append as not thread safe and call it only from EDT.

Edit: Another possible reason for append being not thread-safe: it consists of 2 operations: getLength and insertString, and between the 2, the contents of the document may change. So be careful also with constructs like insertString(getLength(), ...). Without synchronization it's incorrect. AbstractDocument.writeLock may help, but it's protected.

于 2012-10-07T14:19:59.023 回答