JTextArea поток безопасно? - PullRequest
2 голосов
/ 02 февраля 2011

У меня есть некоторый код, который выполняет некоторую инициализацию (включая создание объекта JTextArea), запускает три отдельных потока, а затем эти потоки пытаются обновить JTextArea (т.е. append() до него), но он не работает совсем. На JTextArea ничего не отображается (однако во время инициализации я печатаю на нем несколько тестовых строк, и это работает нормально). В чем дело? Как я могу это исправить? Кроме того, каждый из этих потоков спит случайное количество времени каждый раз, когда ему нужно обновить JTextArea.

Извините, я не предоставил никакого кода, он распределен по нескольким файлам.

Ответы [ 5 ]

4 голосов
/ 02 февраля 2011

Хотя я считаю, что API заявил, что JTextArea # append (...) является потокобезопасным, я слышал о проблемах с ним и рекомендую вызывать его только в EDT.Классическим примером этого является использование SwingWorker и добавление JTextArea в методе процесса с помощью вызова publish.

Мне будет сложно сделать какие-то конкретные предложения, хотя и без кода.Мне действительно интересно, если вы кладете EDT в спящий режим где-то в вашем коде.

Редактировать: согласно вашему комментарию, изучите этот урок: Параллельность в Swing


Редактировать 2: согласно комментарию Тима Перри , потеря безопасности потока и обоснование этого было опубликовано в этой ошибке Java и связано с этимстрока кода, где текст добавляется в Документ JTextArea:

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

Строка разбивается на две строки:

  1. int len=doc.getLength();
  2. doc.insertString(len,str,null);

Проблема в том, что проблема может возникнуть, если документ, doc, изменяется между строками 1 и 2, особенно длина документа.

3 голосов
/ 02 февраля 2011

В Java 1.6 документация для JTextArea.append гласит:

Добавляет данный текст в конец документ. Ничего не делает, если модель равна нулю или строка равна нулю или пустой.

Этот метод является потокобезопасным, хотя большинство методов Swing - нет. Пожалуйста, смотрите Как использовать темы для более информация.

В JDK7 вторая часть отсутствует:

Добавляет данный текст в конец документ. Ничего не делает, если модель равна нулю или строка равна нулю или пустой.

Если вы посмотрите на интерфейс Document (который JTextArea может использовать предоставленный пользователем экземпляр), нет способа добавить текст в поточно-ориентированном виде, даже если реализация поточно-ориентирована. Качание ниток просто нарушено. Я настоятельно рекомендую строго придерживаться AWT EDT при приближении к компонентам Swing.

2 голосов
/ 07 октября 2012

JTextArea Поток безопасен?

Определенно нет. Не в общем. Как утверждали другие, даже метод append не документирован как поточно-ориентированный. Однако Документация Java 7 файла AbstractDocument.insertString четко гласит, что этот метод является поточно-ориентированным.

Использование AbstractDocument.insertString представляется безопасным в соответствии с документами. И это единственный разумный вариант. Обновление строковой модели в потоке графического интерфейса может привести к резкому снижению производительности.

Как насчет JTextArea.append? Я предполагаю, что это зависит от базового Document. Для PlainDocument и DefaultStyledDocument это может быть потокобезопасным. Для других моделей следует проверить соответствующую документацию. Если кто-то не знает, что является базовым документом, он должен рассматривать append как не ориентированный на многопотоковое исполнение и вызывать его только из EDT.

Редактировать: Другая возможная причина того, что append не является поточно-ориентированным: он состоит из 2 операций: getLength и insertString, и между этими двумя значениями содержимое документа может измениться. Так что будьте осторожны и с такими конструкциями, как insertString(getLength(), ...). Без синхронизации это неверно. AbstractDocument.writeLock может помочь, но он защищен.

2 голосов
/ 07 октября 2012

Я доверяю опытным парням, которые предупреждают о вере в безопасность потоков Document. Однако трудно поверить, что приложение так легко использует эту проблему, в результате чего JTextArea вообще ничего не отображает. Возможно, используются другие методы, кроме append, и они вызывают общий сбой. Я прилагаю тестовое приложение, которое я запускаю на Debian с Oracle jre 6 (а также Win7 с java 6 64bit) и не вижу проблем.

В процессе разработки мне пришлось исправить несколько ошибок, в том числе:

  1. Не синхронизируется метод getLength() и insertString(), что привело к неправильному размещению вставки и даже BadLocationException s. Другие темы изменяли документ между этими двумя инструкциями. Даже если бы они были на одной линии:)
  2. Глотание BadLocationException. Я был уверен, что поразить его невозможно, но ошибся.

После осознания вышеизложенного, особенно необходимости создания критической секции для пары getLength() и insertString(), становится очевидным, что JTextArea потерпит неудачу ( см. Ответ Тома Хоутина здесь ). И я увидел, что это действительно так, потому что не все insertString были выполнены успешно, а полученный текст был короче, чем должен быть. Однако эта проблема не возникла с числом циклов 10000, только на 100000. И, глядя на jdk 7 код JTextArea.append , который не делает ничего, кроме изменения базового документа, кажется, что внешняя синхронизация JTextArea будет также сделай.

Вставка в 0 также работала хорошо, без какой-либо синхронизации, хотя для ее завершения потребовался возраст.

Обычно в таких приложениях хочется прокрутить до последней строки. Привет это Вы не можете setCaretPosition из EDT. Так что я не Прокрутите вручную. Мое предложение решить прокрутку в другой ответ .

Если вы, ребята, видите проблемы с этим приложением в вашей системе, пожалуйста, прокомментируйте. Мой вывод таков: Document.insertString является поточно-ориентированным, однако для его эффективного использования вместе с getLength необходима синхронизация.

В следующем коде PlainDocument разделяется на подклассы для создания синхронизированного метода append, который оборачивает getLength и insertString в блокировку. Эта блокировка имеет защищенный доступ, поэтому я не смог бы использовать ее без отдельного класса. Однако внешняя синхронизация также дала правильные результаты.

Кстати: простите за столь большое количество правок. Наконец я реструктурировал этот ответ, узнав больше.

код:

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 голосов
/ 02 февраля 2011

JTextArea.append(..) является потокобезопасным, поэтому его можно безопасно вызывать из разных потоков.

Однако Javadoc .append() утверждает:

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

Итак, убедитесь, что модель JTextArea инициализирована (через соответствующий конструктор).

...