Java Concurrency - веб-приложение - PullRequest
3 голосов
/ 04 февраля 2010

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

Я использую JBoss Seam в сочетании с Hibernate и EHCache на Jetty. Сейчас это один сервер приложений с несколькими ядрами.

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

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

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

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

Сеанс содержит список запросов, по-видимому, этот список обрабатывается несколькими потоками.

@Entity
public class HttpSession 
{
  protected List<HttpRequest> httpRequests;

  @Fetch(FetchMode.SUBSELECT)
  @OneToMany(mappedBy = "httpSession")
  public List<HttpRequest> getHttpRequests()
  {return(httpRequests);}

  ...
}

@Entity
public class HttpRequest
{
  protected HttpSession httpSession;

  @ManyToOne(optional = false)
  @JoinColumn(nullable = false)
  public HttpSession getHttpSession()
  {return(httpSession);}

  ...
}

В этом втором фильтре сервлетов я делаю что-то вроде:

httpSession.getHttpRequests().add(httpRequest);
session.saveOrUpdate(httpSession);

Часть, которая выдает ошибку, - это когда я делаю некоторое сравнение, чтобы увидеть, что изменилось от запроса к запросу:

for(HttpRequest httpRequest:httpSession.getHttpRequests())

Эта строка взорвана с исключением одновременной модификации.

Вещи, чтобы уйти с: 1. Будут ли здесь полезны тесты JMeter? 2. Какие книги вы рекомендуете для написания веб-приложений, которые масштабируются при одновременной загрузке? 3. Я попытался поместить синхронизированный туда, где, как мне кажется, он мне нужен, то есть в методе, который перебирает запросы, но все равно не получается. Что еще мне нужно сделать?

Я добавил несколько комментариев:

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

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

Мне все равно придется решать проблему параллелизма.

Спасибо

Walter

Ответы [ 5 ]

5 голосов
/ 04 февраля 2010

Исключение ConcurrentModificationException возникает, когда любая коллекция изменяется во время итерации по ней. Вы можете сделать это в одном потоке, например ::10000

for( Object o : someList ) {
  someList.add( new Object() );
}

Оберните ваш список с помощью Collections.synchronizedList или верните неизменяемую копию списка.

3 голосов
/ 04 февраля 2010
  1. Я не уверен насчет масштабирования веб-приложений в частности, но Параллелизм Java на практике - фантастическая книга по параллелизму в целом.
  2. Список должен быть заменен версией, которая является потокобезопасной, или все доступ к нему должен быть синхронизирован (читатели и записчики) для одного и того же объекта. Недостаточно синхронизировать только метод, который читает из списка.
0 голосов
/ 04 февраля 2010

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

Любая книга по параллелизму Дуга Ли заслуживает чтения.

0 голосов
/ 04 февраля 2010

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

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

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

  • Сохраняйте значения в ConcurrentHashMap, который использует чередование блокировок для минимизации конфликта блокировок.Затем вы могли бы заставить свой метод get возвращать скопированный список ключей, которые вам нужны, а не полные объекты, и получать к ним доступ по одному за раз прямо с карты.

Как уже упоминалось в другом ответе, Java Concurrency на практике - это великая книга.

0 голосов
/ 04 февраля 2010

Это вызвано тем, что список был изменен другим запросом, в то время как вы все еще выполняете его в одном запросе. Замена List на ConcurrentLinkedQueue (нажмите на ссылку, чтобы увидеть javadoc) должна решить конкретную проблему.

Что касается других ваших вопросов:

1: здесь будут полезны тесты JMeter?

Да, это, безусловно, полезно для стресс-тестирования веб-приложений и выявления ошибок параллелизма.

2: Какие книги вы рекомендуете для написания веб-приложений, которые масштабируются при одновременной загрузке?

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

3: Я пытался поместить синхронизированный туда, где, как мне кажется, он мне нужен, то есть на метод, который перебирает запросы, но он все равно не работает. Что еще мне нужно сделать?

Вам необходимо синхронизировать любой доступ к списку в той же блокировке. Но просто заменить на ConcurrentLinkedQueue проще.

...