我有一些代码执行一些初始化(包括创建一个JTextArea
对象),启动三个单独的线程,然后这些线程尝试更新JTextArea
(即append()
到它),但它根本不起作用。 JTextArea
上没有显示任何内容(但是,在初始化期间,我会在其上打印一些测试线,并且工作正常)。这是怎么回事?我怎样才能解决这个问题?此外,每次必须更新JTextArea
时,每个线程都会休眠一段时间。
抱歉,我没有提供任何代码,它全部分散在几个文件中。
答案 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);
该行分解为两行:
int len=doc.getLength();
doc.insertString(len,str,null);
问题是,如果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上运行,看不出任何问题。
在开发过程中,我不得不修复几个错误,其中包括:
getLength()
和insertString()
方法甚至是BadLocationException
。其他线程正在修改这两个指令之间的文档。即使他们在同一条线上:)BadLocationException
。我确信这是不可能的,但是错了。在意识到上述情况后,特别是需要为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
方法,该方法将getLength
和insertString
包装到锁中。这个锁具有受保护的访问权限,因此如果没有单独的类,我就无法使用它。然而,外部同步也给出了正确的结果。
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
。对于PlainDocument
和DefaultStyledDocument
,它可以是线程安全的。对于其他型号,应检查相关文档。如果一个人不知道什么是底层文档,那么他们应该将append
视为非线程安全,并且只能从EDT调用它。
编辑:append
不是线程安全的另一个可能原因:它由2个操作组成:getLength
和insertString
,在2之间,文档内容可能会发生变化。因此,请注意像insertString(getLength(), ...)
这样的结构。没有同步它是不正确的。 AbstractDocument.writeLock
可能有所帮助,但它受到保护。