2011-02-01 2 views
2

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

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

ответ

4

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

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

Edit: в соответствии с вашим комментарием проверить этот учебник: Concurrency in Swing


Edit 2: как за комментарий по Tim Perry, потеря безопасности потока и рассуждения за это было опубликовано в this Java bug и который должен делать с этой строки кода, где текст добавляется в документ в JTextArea в:

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

линия распадается на две линии:

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

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

+0

Можете ли вы упомянуть о любых хороших учебниках, объясняющих EDT? –

+0

См. Редактирование в моем ответе. –

+0

Спасибо большое :) –

2

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

Однако Javadoc из .append() состояний:

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

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

3

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

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

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

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

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

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

+0

У меня возникли проблемы с пониманием этого: * нет способа добавить текст в потокобезопасной манере, даже если реализация потокобезопасна *, поэтому я попытаюсь рассказать об этом. Наверное, вы имеете в виду: в интерфейсе Document отсутствует операция атома * append *, поэтому для добавления необходимо выполнить два последующих вызова: 'getLength',' insertString'. Невозможно гарантировать, что содержимое не изменится между этими двумя вызовами. – Jarekczek

2

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

В процессе разработки я должен был исправить несколько ошибок, которые включали:

  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 также хорошо работает, без какой-либо синхронизации, хотя для ее завершения потребовались целые вековые годы.

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

Если вы видите проблемы с этим приложением в вашей системе, прокомментируйте. Я пришел к выводу, что 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); 
     } 
    }); 
    } 
} 
+0

+1 впечатлен вашей стойкостью :-) Хотя вы меня где-то потеряли (моя вина из-за отсутствия реального стимула копаться в этом, а не у вас!) - как правило, я просто гарантирую доступ ко всем связанным с колебаниями по EDT и счастливый. Что касается каретки: DefaultCaret имеет свойство updatedPolicy (или подобное, слишком ленив, чтобы искать ... кашель), что достаточно хорошо в большинстве случаев использования. – kleopatra

+0

@ Клеопатра, спасибо! Вы напомнили мне о важной вещи. Для чего все это? ** Эффективность **. Не впечатляют, это результаты (для цикла 10000, на Linux): рабочая нить (как в приведенном выше коде): ** 10s **, invokeLater: ** 15,5s **, invokeAndWait: 59,5s. 'invokeLater' имеет только около 50% потери производительности, поэтому я с вами! Побочный эффект: EDT-решения обновляют каретку автоматически. – Jarekczek

+0

re: прокрутка до последней строки: вы можете сделать это автоматически для вас; ознакомьтесь с политикой обновления каретки по умолчанию: http://docs.oracle.com/javase/8/docs/api/javax/swing/text/DefaultCaret.html#setUpdatePolicy-int- –

2

JTextArea поточно?

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

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

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

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

Смежные вопросы