Java: библиотеки Swing и безопасность потоков - PullRequest
22 голосов
/ 08 октября 2008

Я часто слышал критику по поводу отсутствия безопасности потоков в библиотеках Swing. Тем не менее, я не уверен, что то, что я буду делать в своем собственном коде, может вызвать проблемы:

В каких ситуациях в игру вступает тот факт, что Swing не безопасен для потоков?

Что я должен активно избегать?

Ответы [ 11 ]

26 голосов
/ 08 октября 2008
  1. Никогда не выполняйте долго выполняющиеся задачи в ответ на кнопку, событие и т. Д., Поскольку они находятся в потоке событий. Если вы заблокируете поток событий, ВЕСЬ графический пользовательский интерфейс будет полностью не отвечать, в результате чего ДЕЙСТВИТЕЛЬНО разозлится пользователь. Вот почему Свинг кажется медленным и резким.

  2. Используйте Threads, Executors и SwingWorker для запуска задач НЕ НА EDT (поток отправки событий).

  3. Не обновлять и не создавать виджеты за пределами EDT. Единственный вызов, который вы можете сделать вне EDT, это Component.repaint (). Используйте SwingUtilitis.invokeLater, чтобы обеспечить выполнение определенного кода в EDT.

  4. Используйте Методы отладки EDT и умный внешний вид (например, Вещество , которое проверяет нарушение EDT)

Если вы будете следовать этим правилам, Swing может сделать несколько очень привлекательных и отзывчивых графических интерфейсов

Пример некоторых ДЕЙСТВИТЕЛЬНО потрясающих работ на Swing UI: Palantir Technologies . Примечание: я НЕ работаю на них, просто пример потрясающего свинга. Не позорьте публичную демонстрацию ... Их блог тоже хорош, редок, но хорош

11 голосов
/ 09 октября 2008

Это один из тех вопросов, который радует меня тем, что я купил книгу Робинсона и Воробьева о Swing .

Все, что обращается к состоянию java.awt.Component, должно выполняться внутри EDT, с тремя исключениями: все, что конкретно задокументировано как поточно-ориентированное, например repaint(), revalidate() и invalidate(); любой компонент в пользовательском интерфейсе, который еще не был реализован ; и любой Компонент в Апплете до того, как start() этого Апплета был вызван.

Методы, специально созданные для обеспечения многопоточной безопасности, настолько необычны, что часто достаточно просто запомнить те, которые являются; Вы также можете избежать неприятностей, если предположить, что таких методов нет (например, совершенно безопасно обернуть вызов перерисовки в SwingWorker).

Реализовано означает, что Компонент является либо контейнером верхнего уровня (например, JFrame), для которого был вызван любой из setVisible(true), show() или pack(), либо он был добавлен к реализованному компоненту. Это означает, что совершенно нормально построить свой пользовательский интерфейс в методе main (), как это делают многие учебные примеры, поскольку они не вызывают setVisible(true) в контейнере верхнего уровня, пока каждый компонент не будет добавлен в него, не настроены шрифты и границы и т. д.

По тем же причинам совершенно безопасно построить пользовательский интерфейс апплета в его методе init(), а затем вызвать start() после того, как все будет построено.

Включение последующих изменений компонентов в Runnables для отправки в invokeLater() становится легко получить сразу после выполнения всего лишь нескольких раз. Единственное, что меня раздражает, это чтение состояния компонента (скажем, someTextField.getText()) из другого потока. Технически это тоже должно быть заключено в invokeLater(); на практике это может сделать уродливый код быстрым, и я часто не беспокоюсь, или я осторожен, чтобы получить эту информацию в начальный момент обработки события (как правило, в любом случае самое подходящее время для этого).

8 голосов
/ 08 октября 2008

Дело не только в том, что Swing не является потокобезопасным (не так много), но он враждебен потокам. Если вы начнете выполнять Swing в одном потоке (кроме EDT), то в случаях, когда Swing переключается на EDT (не задокументировано), вполне могут возникнуть проблемы с безопасностью потоков. Даже текст Swing, который стремится быть поточно-ориентированным, не является поточно-ориентированным (например, для добавления к документу сначала нужно найти длину, которая может измениться до вставки).

Итак, делайте все манипуляции с Swing на EDT. Обратите внимание, что EDT - это не та нить, к которой вызывается основная, поэтому запустите ваши (простые) приложения Swing, как этот шаблон:

class MyApp {
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
            runEDT();
        }});
    }
    private static void runEDT() {
        assert java.awt.EventQueue.isDispatchThread();
        ...
4 голосов
/ 21 декабря 2009

Альтернативой использованию интеллектуальных скинов, подобных веществу, является создание следующего служебного метода:

public final static void checkOnEventDispatchThread() {
    if (!SwingUtilities.isEventDispatchThread()) {
        throw new RuntimeException("This method can only be run on the EDT");
    }
}

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

Обратите внимание, что интеллектуальные скины, конечно, могут обеспечить дополнительное покрытие, а также только это.

3 голосов
/ 08 октября 2008

Активно избегайте работы Swing вообще, кроме как в потоке диспетчеризации событий. Swing был написан так, чтобы его было легко расширять, и Sun решила, что однопоточная модель лучше для этого.

У меня не было проблем при следовании моему совету выше. Есть некоторые обстоятельства, когда вы можете «качаться» из других тем, но я никогда не нашел в этом необходимости.

2 голосов
/ 09 октября 2008

Фраза «поток-небезопасный» звучит так, как будто есть что-то плохое по своей природе (вы знаете ... «безопасно» - хорошо; «небезопасно» - плохо). Реальность такова, что безопасность потоков стоит дорого - безопасные для потоков объекты зачастую гораздо сложнее реализовать (а Swing достаточно сложен, даже если он есть).

Кроме того, потокобезопасность достигается с помощью стратегий блокировки (медленная) или сравнения и замены (сложная). Учитывая, что GUI взаимодействует с людьми, которые, как правило, непредсказуемы и их сложно синхронизировать, многие инструментарии решили направить все события через один насос событий. Это верно для Windows, Swing, SWT, GTK и, возможно, других. На самом деле я не знаю ни одного инструментария с графическим интерфейсом, который действительно поточно-ориентирован (то есть вы можете манипулировать внутренним состоянием его объектов из любого потока).

Вместо этого обычно делается то, что GUI предоставляют способ справиться с поточной небезопасностью. Как отмечали другие, Swing всегда предоставлял несколько упрощенный SwingUtilities.invokeLater (). Java 6 включает превосходный SwingWorker (доступный для предыдущих версий на Swinglabs.org). Существуют также сторонние библиотеки, такие как Foxtrot, для управления потоками в контексте Swing.

Известность Swing заключается в том, что дизайнеры приняли легкомысленный подход, предполагая, что разработчик будет поступать правильно, а не останавливать EDT или модифицировать компоненты извне EDT. Они заявили о своей политике многоплановости громко и четко, и разработчики должны следовать ей.

Тривиально заставить каждый API свинга отправлять работу в EDT для каждого набора свойств, аннулирования и т. Д., Что делает его поточно-ориентированным, но за счет значительных замедлений. Вы даже можете сделать это самостоятельно, используя АОП. Для сравнения, SWT генерирует исключения, когда к компоненту обращаются из неправильного потока.

2 голосов
/ 08 октября 2008

Если вы используете Java 6, то SwingWorker, безусловно, самый простой способ справиться с этим.

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

Это можно найти с помощью метода SwingUtilities.isEventDispatchThread (), чтобы сообщить вам, если вы в нем (как правило, не очень хорошая идея - вы должны знать, какой поток активен).

Если вы не в EDT, то вы используете SwingUtilities.invokeLater () и SwingUtilities.invokeAndWait () для вызова Runnable в EDT.

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

1 голос
/ 01 мая 2009

Для получения более подробной информации о многопоточности «Укрощение потоков Java» Аллена Холуба - более старая книга, но она отлично читается.

Holub, действительно продвигает отзывчивый пользовательский интерфейс и детализирует примеры и способы решения проблем.

http://www.amazon.com/Taming-Java-Threads-Allen-Holub/dp/1893115100 http://www.holub.com/software/taming.java.threads.html

Мне нравится раздел "Если бы я был королем" в конце там.

1 голос
/ 16 октября 2008

invokeLater () и invokeAndWait () действительно ДОЛЖНЫ использоваться, когда вы выполняете любое взаимодействие с компонентами GUI из любого потока, который НЕ является EDT.

Это может работать во время разработки, но, как и большинство одновременных ошибок, вы начнете видеть странные исключения, которые кажутся совершенно не связанными и происходят недетерминированно - обычно обнаруживаются ПОСЛЕ того, как вас отправили реальные пользователи. Не хорошо.

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

Да, это ужасно переносит каждый вызов метода обратно в EDT в экземпляре Runnable, но для вас это Java. Пока мы не получим закрытия, вам просто придется с этим жить.

1 голос
/ 12 октября 2008

Обратите внимание, что даже интерфейсы модели не являются поточно-ориентированными. Размер и содержимое запрашиваются отдельными методами get, поэтому их невозможно синхронизировать.

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

Обновление состояния модели всегда в EDT позволяет избежать этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...