Если геттеры моделей Swing не являются поточно-ориентированными, как вы с ними справляетесь? - PullRequest
15 голосов
/ 19 января 2010

Хорошо известно, что обновление графического интерфейса Swing должно выполняться исключительно в EDT. Меньше рекламируется, что чтение материала из GUI должно / должно также быть сделано в EDT. Например, давайте возьмем метод isSelected () ButtonModel, который сообщает (например) состояние ToggleButton («вниз» или «вверх»).

В каждом примере, который я видел, isSelected() свободно запрашивается из основного потока или любого другого потока. Но когда я смотрю на реализацию DefaultButtonModel, она не синхронизируется, и значение не является изменчивым. Так что, строго говоря, isSelected() может вернуть мусор, если он читается из любого другого потока, кроме того, из которого он установлен (то есть EDT, когда пользователь нажимает кнопку). Или я ошибаюсь?

Первоначально я думал об этом, когда был шокирован элементом # 66 в Эффективной Java Блоха, этот пример:

public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while(!stopRequested) i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

Вопреки тому, что кажется, эта программа никогда не завершается, по крайней мере, на некоторых машинах. Обновление флага stopRequested из основного потока невидимо для фонового потока. Ситуацию можно исправить с помощью синхронизированных геттеров и сеттеров или установив флаг volatile.

Итак:

  1. Неправильно ли запрашивать состояние модели Swing вне EDT (строго говоря)?
  2. Если нет, то как?
  3. Если да, как вы справляетесь с этим? Удачей или каким-то умным решением? InvokeAndWait

Ответы [ 6 ]

4 голосов
/ 19 января 2010

Да, это неправильно; если оба потока не синхронизируют на одном и том же объекте или не используют какой-либо другой барьер памяти, например энергозависимый, как определено в JMM , то один или другой может наблюдать несогласованное содержимое памяти. Период. Конец истории. Нарушение этого может сработать на некоторых, даже на многих архитектурах, но в конечном итоге это укусит вас за уши.

Проблема здесь в том, что за некоторыми исключениями, когда вы предоставляете модель, например, то, на что ссылался Адамски, код Swing не будет синхронизироваться ни с чем.

И стоит отметить, что JMM (модель памяти Java) изменилась с JSR-133 в Java 5 очень важными способами, поэтому поведение с J4 и более ранней JVM вполне может дать результаты, отличные от J5 и более поздних. Пересмотренный JMM, как и следовало ожидать, значительно улучшен.

4 голосов
/ 19 января 2010
  1. Нет, не так, но, как и при любом межпотоковом обмене данными, вам необходимо убедиться, что вы предоставили соответствующие механизмы обеспечения безопасности потоков, если решите это сделать (например, используйте synchronized или volatile). Например, я обычно пишу свои собственные TableModel реализации, обычно сидя на List<X>, где X - мой бизнес-объект. Если я собираюсь запросить список в других потоках, я сделаю это синхронизированным Collection. Также стоит отметить, что я бы никогда не обновил List из других потоков; только запрос.
  2. Потому что это точно такая же ситуация, как и с любым другим многопоточным приложением.
  3. Обычно я синхронизирую коллекцию (см. 1).

Протест

Несмотря на мой ответ выше, я обычно не не обращаюсь к моделям непосредственно за пределами EDT. Как упоминает Карл, если действие по выполнению обновления вызывается с помощью какого-либо действия с графическим интерфейсом, нет необходимости выполнять какую-либо синхронизацию, поскольку код уже выполняется EDT. Однако, если я хочу выполнить некоторую фоновую обработку, которая приведет к изменению модели, я обычно вызываю SwingWorker, а затем присваиваю результаты doInBackground() из метода done() (т.е. в EDT). Это чище ИМХО, так как метод doInBackground() не имеет побочных эффектов.

2 голосов
/ 19 января 2010

Swing в целом не только не ориентирован на многопотоковое исполнение, но и враждебен к многопоточности Однако большинство моделей, кроме текста Swing, не зависят от потоков. Это означает, что вы можете использовать модели в любом потоке, если вы используете стандартную защиту потока. Swing text был попыткой быть поточно-ориентированным, но потерпел неудачу и на самом деле враждебен по отношению к потокам. Используйте Document s только из потока рассылки событий (EDT).

Обычный совет - запускать потенциально длительные (обычно блокирующие) задачи вне EDT. Тем не менее, многопоточность сложно. SwingWorker - это популярный способ вызвать плохой дизайн - избегайте его. Постарайтесь разделить код EDT и код, не являющийся EDT. Старайтесь не использовать изменяемое состояние.

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

1 голос
/ 19 января 2010

Если да, как вы справляетесь с этим?Удачей или каким-то умным решением?InvokeAndWait?

Другие уже упоминали SwingWorker, и вы уже знаете о invoke* методах.javax.swing.Timer также может быть полезным.Для безопасного многопоточного программирования не существует «серебряных пуль», хотя хороший дизайн будет иметь большое значение.

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

  public static class ThreadUnsafeAccessException extends RuntimeException {
    private static final long serialVersionUID = 1L;
  }

  @Override public Object getElementAt(int index) {
    if (!SwingUtilities.isEventDispatchThread()) {
      throw new ThreadUnsafeAccessException();
    }
    return decorated.getElementAt(index);
  }

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

1 голос
/ 19 января 2010
  1. Я никогда не беспокоился об этом, но, строго говоря, да, это, вероятно, неправильно.
  2. N / A.
  3. Я допускаю изменения в обновлении GUI (через слушателей)модели собственной конструкции, которые не содержатся или не построены Swing.Нет необходимости спрашивать GUI, потому что GUI обновляет модель.

Поскольку я сам создаю модели (это может быть что-то очень простое, например String with getter / setter и PropertyChangeSupport),Я могу синхронизировать аксессоры, если захочу.Я редко сталкиваюсь с нестабильным поведением из-за конфликта потоков или чего-то подобного.

0 голосов
/ 19 января 2010

Это отличный вопрос, и у Software Monkey есть правильный ответ. Если вы хотите выполнить эффективную многопоточность, вы должны полностью понимать Java Память Модель (JMM). Обратите внимание, что модель памяти Java изменилась с JSR-133, поэтому многие старые примеры потоков, которые вы найдете, будут просто ошибочными. Например, ключевое слово volatile изменилось с почти бесполезного на теперь почти такое же принудительное, как синхронизированное ключевое слово.

Кроме того, теперь у нас есть настоящие, вездесущие многоядерные процессоры, что означает настоящую многопоточность. Многопоточность до тех пор, пока несколько лет назад имитировали его только на одноядерных процессорах, вот почему так много плохого примера кода. Раньше работал. Теперь не будет.

Серьезно, если Джошуа Блох может ошибиться, действуй очень осторожно. Это сложные вещи (и да, его код неправильный. Сделайте эту переменную изменчивой, и она будет работать во всех случаях).

Ах да, Адамски правильно понял. Используйте SwingWorker, чтобы убедиться, что долгосрочный код выполняется в фоновом потоке, и что, когда он выполняет манипулирование данными, он должен обращаться к Swing по методу done(). Если вам нужно прочитать, сделайте это до того, как сделает ваш SwingWorker, и введите информацию final.

Пример кода, который будет вызываться в EDT, скорее всего, в ActionListener или в некотором таком (psuedocode):

public void someOverlySimpleExampleThatIsCalledOnEDT() {
   /*
    * Any code run here is guaranteed to happen-before 
    * the start of a background thread.
    */
   final String text = myTextField().getText();
   SwingWorker sw = new SwingWorker() {
      private volatile List data;
      public Object doInBackground() {
         //happens in background thread. No Swing access
         data = myDao.getDataFromInternet(text);
         return null;
      }
      public void done() {
         //Happens in EDT. Swing away Merrill
         loadDataIntoJTable(data);
      }
   }
   sw.execute();
   /* 
    * Any code run here happens concurrently with doInBackground(). Be careful. 
    * Usually leave empty
    */
}
...