2010-01-19 2 views
15

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

В каждом примере, который я видел, 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. запрашивает состояние качелей модели вне EDT (строго говоря) не так?
  2. Если нет, то почему?
  3. Если да, то как вы справляетесь с этим? К счастью, или каким-то умным обходным решением? InvokeAndWait?

ответ

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

Caveat

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

+1

+1 для приятного подробного объяснения. –

+2

Я должен был бы не согласиться с №1, потому что это вводит в заблуждение новичков. Им следует изучить правило «доступ к качелям вне EDT - это не-нет» и только после того, как они полностью поймут, почему они должны нарушать правила. Новичок только увидит ваши первые три слова «Нет, не ошибаюсь ...». Не говоря уже о том, что потоковая передача достаточно сложна, так как она не беспокоится о том, правильно ли вы синхронизируете свою модель. Единственная причина, по которой я вас не проголосовал, - это то, что ваш раздел Caveat - это правильный способ справиться с этим. – Nemi

+0

Приношу свои извинения за то, что вы назвали Joonas «новичком». :) – Nemi

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

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

+3

Re: # 1, вы должны беспокоиться об этом, потому что доступ к качелям вне EDT плохой плохой. Конечно, вы никогда не видите проблемы, но я гарантирую, что ваши клиенты будут, и они не смогут объяснить, что происходит, потому что это так прерывисто, поэтому ваша программа навсегда будет шелушащейся. – Nemi

+0

Как я продолжаю объяснять, у меня нет причин беспокоиться, потому что я «инстинктивно» делаю все правильно: я кодирую таким образом, что мне никогда не нужно иметь доступ к свойствам компонента GUI из-за пределов EDT , Мои объекты модели обновляются из EDT, и я могу сделать их потокобезопасными. Вот почему у меня мало жалоб клиентов, а не на эту тему. Кроме того, я стараюсь не беспокоиться ни о чем, потому что иначе мой желудок вызывает у меня проблемы. Поэтому я не буду беспокоиться о вашем предупреждении и опускании;) –

+0

@ Карл, хотя и ценю тот факт, что вы «инстинктивно» поступаете правильно, вы, к сожалению, плохо советовали Иоонасу. Никаких жестких чувств. – Nemi

4

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

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

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

+2

Это правильный ответ. JMM http://www.cs.umd.edu/~pugh/java/memoryModel/ трудно понять. Попытка найти умные способы обойти это надежный способ создания багги-приложений. Придерживайтесь правил, пока не почувствуете, что находитесь на уровне экспертов. – Nemi

+0

Думая о архитектуре, вероятно, не лучший способ взглянуть на нее. Компиляторы времени выполнения используют JMM (например, используя регистры). На самом деле не стоит пытаться угадать, какие странные вещи компилятор будет делать./Пред-1.5 JMM не имеет смысла и никогда не была правильно реализована. В реализации Sun 1.4 используется 1.5 JMM. –

+0

@Tom: Мое мнение состояло в том, что тестирование не выявит каких-либо проблем, вполне возможно, потому что на многих архитектурах их может не быть. –

0

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

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

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

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

Пример кода, который будет называться по восточному времени, скорее всего, в 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 
    */ 
} 
+0

Любопытно, что ваш пример (насколько я понимаю) делает именно ту ошибку, о которой меня беспокоило: вы вызываете 'getText()' в произвольном потоке, и поэтому вы можете получить неправильный контент в переменной 'text'. –

+0

Еще пара комментариев: 1. Код Блоха намеренно ошибочен, это (потрясающая) демонстрация IMHO. 2. Я согласен с вами в том, что увеличение количества ядер приведет к появлению новых ошибок параллелизма в ближайшем будущем. Мы больше не можем его подделывать :-) –

+0

@Joonas, вы правы, мой код не был ясен. Это код, который будет вызываться в EDT, скорее всего, в ActionListener. Таким образом, вы знаете, что весь доступ за пределами SwingWorker находится на EDT. Обычно нажатие кнопки или другое подобное действие произошло. Я редактировал имя метода, чтобы сделать его более понятным. – Nemi

2

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

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

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

+3

«SwingWorker - популярный способ вызвать плохой дизайн - избегать его». -Не уверен, что я согласен с тобой там. Как еще вы предложили бы выполнить фоновое редактирование? – Adamski

+0

Я согласен с Адамски в том, что если вы не согласны с использованием SwingWorker, было бы неплохо иметь рассуждения позади него. – Nemi

1

Если да, то как вы с этим справитесь? К счастью, или каким-то умным обходным решением? 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); 
    } 

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