JTextArea thread safe?
I have some code that does some initialization (including making a JTextArea
object), starts three separate threads, and then these threads try to update the JTextArea
(i.e. append()
to it), but its not working at all. Nothing shows up on the JTextArea
(however, during the initializ开发者_开发知识库ation, I print some test lines onto it, and that works fine). What's going on? How can I fix this? Also, each of those threads sleeps a random amount of time every time it has to update the JTextArea
.
Sorry I haven't provided any code, its all spread out over several files.
Although I believe the API has stated that JTextArea#append(...) is thread safe, I've heard of problems with it and would recommend that this only be called on the EDT. The classic example of this is to use a SwingWorker and append to the JTextArea in the process method by calling publish.
For me, it'll be hard to make any specific suggestions to you though without code. I do have to wonder though if you're putting the EDT to sleep somewhere in your code.
Edit: as per your comment check out this tutorial: Concurrency in Swing
Edit 2: as per comment by Tim Perry, loss of thread safety and the reasoning behind this has been posted in this Java bug and which has to do with this line of code where text is added to the JTextArea's Document:
doc.insertString(doc.getLength(), str, null);
The line decomposes into two lines:
int len=doc.getLength();
doc.insertString(len,str,null);
The issue is that a problem can occur if the Document, doc, changes between lines 1 and 2, especially the Document length.
In Java 1.6, the documentation for JTextArea.append
says:
Appends the given text to the end of the document. Does nothing if the model is null or the string is null or empty.
This method is thread safe, although most Swing methods are not. Please see How to Use Threads for more information.
In JDK7 the second part is missing:
Appends the given text to the end of the document. Does nothing if the model is null or the string is null or empty.
If you look at the Document
interface (which JTextArea
can use a user supplied instance), there is no way to append text in a thread-safe manner even if the implementation is thread-safe. Swing threading is just broken. I strongly suggest sticking rigidly to the AWT EDT when going anywhere near Swing components.
JTextArea.append(..)
is thread safe so it should be safe to call it from different threads.
However the javadoc of .append()
states:
Does nothing if the model is null or the string is null or empty.
So, make sure that the model of JTextArea is initialized (via appropriate constructor).
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.
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:
- Not synchronizing
getLength()
andinsertString()
method which resulted in wrong insertion placement and evenBadLocationException
s. Other threads were modifying the document between these 2 instructions. Even if they were on the same line :) - 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);
}
});
}
}
精彩评论