JTextArea线程安全吗?

时间:2011-02-01 23:08:09

标签: java multithreading swing user-interface jtextarea

我有一些代码执行一些初始化(包括创建一个JTextArea对象),启动三个单独的线程,然后这些线程尝试更新JTextArea(即append()到它),但它根本不起作用。 JTextArea上没有显示任何内容(但是,在初始化期间,我会在其上打印一些测试线,并且工作正常)。这是怎么回事?我怎样才能解决这个问题?此外,每次必须更新JTextArea时,每个线程都会休眠一段时间。

抱歉,我没有提供任何代码,它全部分散在几个文件中。

5 个答案:

答案 0 :(得分:4)

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

对我来说,如果没有代码,很难向你提出任何具体的建议。我不得不怀疑,如果你让EDT在代码中的某个地方睡觉。

修改:根据您的评论,请查看本教程:Concurrency in Swing


编辑2:根据Tim Perry的评论,线程安全性的丢失及其背后的推理已发布在this Java bug中,这与此行代码有关,其中文本已添加到JTextArea的文件:

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

该行分解为两行:

  1. int len=doc.getLength();
  2. doc.insertString(len,str,null);
  3. 问题是,如果Document,doc在第1行和第2行之间发生更改,尤其是文档长度,则可能会出现问题。

答案 1 :(得分:3)

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

  

将给定文本追加到末尾   该文件。什么都没有   model为null或字符串为null或   空。

     

此方法是线程安全的,   虽然大多数Swing方法都没有。   有关更多信息,请参阅如何使用线程   信息。

在JDK7中,第二部分缺失:

  

将给定文本追加到末尾   该文件。什么都没有   model为null或字符串为null或   空。

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

答案 2 :(得分:2)

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

然而.append()的javadoc声明:

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

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

答案 3 :(得分:2)

我相信有经验的人会警告我们相信Document的线程安全。但是,很难相信应用程序如此容易地利用此问题导致JTextArea根本不显示任何内容。除了append之外,可能会使用其他一些方法,它们会导致一般性失败。我附上了一个测试应用程序,我使用Oracle jre 6(以及带有java 6 64位的Win7)在Debian上运行,看不出任何问题。

在开发过程中,我不得不修复几个错误,其中包括:

  1. 未同步导致错误插入位置的getLength()insertString()方法甚至是BadLocationException。其他线程正在修改这两个指令之间的文档。即使他们在同一条线上:)
  2. 吞咽BadLocationException。我确信这是不可能的,但是错了。
  3. 在意识到上述情况后,特别是需要为getLength()insertString()对创建关键部分,显然JTextArea会失败(see Tom Hawtin's answer here)。我看到它实际上是这样做的,因为不是每个insertString都被成功执行,结果文本比它应该更短。但是,循环计数10000,仅在100000时没有出现此问题。并且查看jdk 7 code of JTextArea.append,除了修改基础文档之外什么都没做,似乎外部同步JTextArea也会这样做。

    在0处插入也运行良好,没有任何同步,尽管需要很长时间才能完成。

    通常在这样的应用程序中,人们想要滚动到最后一行。嘿,这是awt。你不能setCaretPosition退出EDT。所以我没有。手动滚动。我建议解决滚动问题的方法是another answer

    如果您在系统上发现此应用程序出现问题,请发表评论。我的结论是 Document.insertString是线程安全的,但是为了有效地使用它,与getLength一起,同步是必要的。

    在以下代码中,PlainDocument被子类化以创建同步append方法,该方法将getLengthinsertString包装到锁中。这个锁具有受保护的访问权限,因此如果没有单独的类,我就无法使用它。然而,外部同步也给出了正确的结果。

    BTW:很抱歉这么多编辑。最后,我在了解了更多内容后重新构建了这个答案。

    代码:

    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);
            }
        });
      }
    }
    

答案 4 :(得分:2)

  

JTextArea线程安全?

绝对不是。不一般。正如其他人所说,即使append方法不再被记录为线程安全。但是Java 7 documentaion of AbstractDocument.insertString明确指出此方法是线程安全的。

根据文档,使用AbstractDocument.insertString似乎是安全的。这是唯一合理的选择。在gui线程中更新字符串模型将会造成严重的性能损失。

JTextArea.append怎么样?我认为这取决于潜在的Document。对于PlainDocumentDefaultStyledDocument,它可以是线程安全的。对于其他型号,应检查相关文档。如果一个人不知道什么是底层文档,那么他们应该将append视为非线程安全,并且只能从EDT调用它。

编辑:append不是线程安全的另一个可能原因:它由2个操作组成:getLengthinsertString,在2之间,文档内容可能会发生变化。因此,请注意像insertString(getLength(), ...)这样的结构。没有同步它是不正确的。 AbstractDocument.writeLock可能有所帮助,但它受到保护。