Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swing JTextArea multithreading problem - InterruptedException

I have a simple console application that runs calculations in several threads (10-20 of them). Now I'm trying to create a simple GUI that allows me to select the file to process and prints logs from all threads.

So, I created a swing GUI with JTextArea for my log and a method to log information to the log:

public synchronized void log(String text) {
    logArea.append(text);
    logArea.append("\n");

    if (logArea.getDocument().getLength() > 50000) {
        try {
            logArea.getDocument().remove(0,5000);
        } catch (BadLocationException e) {
            log.error("Can't clean log", e);
        }
    }

    logArea.setCaretPosition(logArea.getDocument().getLength());
}

However, the setCaretPosition method sometimes deadlocks on waiting some lock, and append sometimes throws InterruptedException.

Exception in thread "Thread-401" java.lang.Error: Interrupted attempt to aquire write lock
at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1334)
at javax.swing.text.AbstractDocument.insertString(AbstractDocument.java:687)
at javax.swing.text.PlainDocument.insertString(PlainDocument.java:114)
at javax.swing.JTextArea.append(JTextArea.java:470)
at lt.quarko.aquila.scripts.ui.ScriptSessionFrame.log(ScriptSessionFrame.java:215)

I'm a total newbie in Swing, so I can not understand what am I doing wrong here?

Thanks in advance.

like image 784
bezmax Avatar asked Dec 22 '10 08:12

bezmax


3 Answers

You don't want to manipulate Swing objects directly from another thread, you want to post manipulations to its event queue.

like image 133
Mud Avatar answered Nov 11 '22 21:11

Mud


You should not update ui component from other threads, you should use EventDispatchThread. Here is the solution ;

 public synchronized void log(String text) {
        Runnable  runnable = new Runnable() {
            public void run(){
                logArea.append(text);
                logArea.append("\n");
                if (logArea.getDocument().getLength() > 50000) {
                    try {
                        logArea.getDocument().remove(0, 5000);
                    } catch (BadLocationException e) {
                        log.error("Can't clean log", e);
                    }
                }
                logArea.setCaretPosition(logArea.getDocument().getLength());
            }
        }
        SwingUtilities.invokeLater(runnable);

    }
like image 40
Gursel Koca Avatar answered Nov 11 '22 22:11

Gursel Koca


Max, you didn't mention, that you interrupt the thread. But you surely did. So your question consists actually of 2 separate questions.

append sometimes throws InterruptedException

I just fell into the same situation and don't know how to handle it. When I interrupt the thread, then Document.insertString fails throwing this kind of error.

Others are not quite right about putting all in EDT thread. JTextArea.append method is thread safe, so it needn't be wrapped. The only method you call that you should not (in work thread) is setCaretPosition. So why you accept the invokeLater answer? Probably because putting document access in one thread removed all locking problems. See AbstractDocument.writeLock open jdk code, that explains a bit this Error.

So it looks like putting Document writes in EDT thread is really necessary, but only when one wants to interrupt the thread. And as a workaround for pretty unkind AbstractDocument behaviour, that throws an Error in this case.

I came up with the following workaround for Document Error. It's not quite clean, because the thread may be unfortunately interrupted right after setting bInterrupted flag. But this may be avoided by performing Thread.interrupt() in a controlled, synchronized way.

// test the flag and clear it (interrupted() method does clear it)
boolean bInterrupted = Thread.interrupted();
m_doc.insertString(m_doc.getLength(), s, null);
// restore the original interrupted state
if (bInterrupted)
  Thread.currentThread().interrupt();

setCaretPosition method sometimes deadlocks on waiting some lock

Here is my solution for caret update. I could simply go with invokeLater, but I wanted to avoid superfluous calls, so I added an additional flag:

/** <code>true</code> when gui update scheduled. This flag is to avoid
  * multiple overlapping updates, not to call
  * <code>invokeLater</code> too frequently.
 */
private volatile boolean m_bUpdScheduled;

/** Updates output window so that the last line be visible */
protected void update()
{
  if (!m_bUpdScheduled) {
    m_bUpdScheduled = true;
    EventQueue.invokeLater(new Runnable() {
        public void run() {
          m_bUpdScheduled = false;
          try {
            m_ebOut.setCaretPosition(m_doc.getLength());
          }
          catch (IllegalArgumentException iae) {
            // doc not in sync with text field - too bad
          }
        }
    });
  }
}
like image 2
Jarekczek Avatar answered Nov 11 '22 22:11

Jarekczek