2011-02-01 100 views
2

我有一些代码可以做一些初始化(包括制作一个JTextArea对象),启动三个独立的线程,然后这些线程尝试更新JTextArea(即append()),但它根本不工作。 JTextArea没有任何显示(但是,在初始化期间,我打印了一些测试线,并且工作正常)。这是怎么回事?我怎样才能解决这个问题?此外,每次线程都会随机调整时间,以便每次更新JTextAreaJTextArea线程安全吗?

对不起,我没有提供任何代码,它的全部分布在几个文件。

回答

4

尽管我相信API已经声明JTextArea#append(...)是线程安全的,但我听说它有问题,并且会建议仅在EDT上调用它。这个的典型例子是使用SwingWorker并通过调用publish将其附加到process方法中的JTextArea。

对我来说,虽然没有代码,但很难向你提出任何具体的建议。我不得不怀疑,如果你让美国东部时间在你的代码的某个地方睡觉。

编辑:根据您的评论看看这个教程: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);

的问题是,可能会出现如果文档,文档,线1和2之间变化,特别是文献长度的问题。

+0

你可以提到任何解释EDT的优秀教程吗? – 2011-02-01 23:14:01

+0

请参阅我的答案中的编辑。 – 2011-02-01 23:15:32

+0

非常感谢:) – 2011-02-01 23:24:11

2

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

然而.append()状态的Javadoc:

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

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

3

在Java 1.6,为JTextArea.append的文件说:

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

这种方法是线程安全的, 虽然大多数Swing方法不是。 请参阅如何使用主题获取更多 信息。

在JDK7第二部分丢失:

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

如果你看一下Document接口(其中JTextArea可以使用用户提供的实例),有没有办法在追加即使实现是线程安全的一个线程安全的方式文本。 Swing线程刚刚打破。我强烈建议在走到Swing组件附近的任何地方时严格遵守AWT EDT。

2

我相信有经验的人会相信Document的线程安全。然而,很难相信应用程序如此轻易地利用这个问题,导致JTextArea什么都没有显示。也许使用其他方法,除了append,它们会导致一般故障。我附加了一个测试应用程序,我使用Oracle jre 6运行Debian(也使用java 6 64bit的Win7),并且没有发现任何问题。

在开发过程中,我不得不修正一些错误,其中包括:

  1. 没有进行同步getLength()insertString()方法,这就造成了误插位置,甚至BadLocationException秒。其他线程正在修改这两条指令之间的文件。即使他们在同一条线上:)
  2. 吞咽BadLocationException。我确定无法击中它,但是错了。

实现了getLength()insertString()对创建临界区的上方,尤其需要后,很明显,JTextArea会失败(see Tom Hawtin's answer here)。而且我看到它实际上是这样做的,因为不是每个insertString都成功执行,并且结果文本比它应该短。然而,这个问题并没有发生在循环计数10000,仅在100000处。并且调查jdk 7 code of JTextArea.append,它只做了修改底层文档,看起来外部同步JTextArea也会这样。

虽然花费了很长时间才完成,但在0时插入也很好,没有任何同步。

通常在这样的应用程序中想要滚动到最后一行。嘿,这是awt。您无法在EDT中使用setCaretPosition。所以我不这样做。手动滚动。我的建议解决滚动是在another answer

如果你们在你的系统上看到这个应用程序的问题,请评论。我的结论是,Document.insertString是线程安全的,但要有效地使用它,连同getLength,同步是必要的。

在下面的代码PlainDocument是子类来创建同步append方法,它包装getLengthinsertString到一个锁。这个锁已经保护了访问权限,所以我不能在没有单独的类的情况下使用它。然而外部同步也给出了正确的结果。

顺便说一句:对不起,这么多的编辑。最后,我在学习更多之后重构了这个答案。

代码:

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); 
     } 
    }); 
    } 
} 
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可能会有帮助,但它是受保护的。