2015-09-08 2 views
1

Этот вопрос больше о том, спрашивает ли мой способ сделать что-то «правильно» или нет. У меня есть программа, которая включает в себя постоянное обновление графических компонентов. Для этого у меня есть метод ниже.Конкурентоспособная модификация и синхронизация arraylists

public void update(){ 
    for (BaseGameEntity movingEntity : movingEntityList) { 
     ((MovingEntity)movingEntity).update(); 
    } 
} 

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

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

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

public void update(){ 
    try{ 
     for (BaseGameEntity movingEntity : movingEntityList) { 
      ((MovingEntity)movingEntity).update(); 
     } 
    }catch(ConcurrentModificationException e){ 
      //Do Nothing 
    } 
} 

Однако, мой вопрос заключается в том, является ли это «правильным» способом решения этой проблемы? Должен ли я делать что-то похожее на то, что указано в this answer? Каков «правильный» способ справиться с этой проблемой, если мой не так? Я не смотрю конкретно на способы сделать мой поток arraylist безопасным, например, через синхронизированные списки, я специально спрашиваю, является ли мой метод допустимым методом или если есть какая-то причина, я должен избегать этого и фактически использовать синхронизированный список.

ответ

2

Правильный путь будет синхронизировать список с Collections.synchronizedList():

List list = Collections.synchronizedList(new ArrayList()); 
     ... 
    synchronized (list) { 
     Iterator i = list.iterator(); // Must be in synchronized block 
     while (i.hasNext()) 
      foo(i.next()); 
    } 

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

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

+0

Как уже говорилось, моя программа работает очень хорошо, как и у меня. Я технически пропустил несколько обновлений, но дополнения и удаления настолько малы, что это не имеет значения. Поэтому мои основные вопросы: если я буду беспокоиться о синхронизации, если мой путь уже работает. – zephyr

1

Является ли это «правильным» способом решения этой проблемы?

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

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

Должен ли я быть [с использованием synchronized]?

Это также жизнеспособное решение. Разница в том, что обновление больше не сможет работать, когда выполняется обновление. Это может быть нежелательным, если критический момент звонка на update является критическим (но не обязательно обновлять все при каждом вызове).

+0

В зависимости от 'ConcurrentModificationException' не подходит для меня вариант [JavaDoc] (http://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html) заявляет: «Было бы неправильно писать программу, зависящую от этого исключения за ее правильность» – vanOekel

+2

@vanOekel С этим решением он не * зависит * от 'ConcurrentModificationException', он просто * игнорирует * это (и это можно сделать безопасно ** в этом конкретном случае **) – Marco13

+0

@vanOekel Документ означает что-то еще - программу, которая * ожидает, что это исключение будет выбрано в определенных ситуациях, на которые реагирует программа.Решение OP, с другой стороны, выбирает * игнорировать * это исключение, которое отличается от того, чтобы полагаться на исключение для правильности. На самом деле код OP с радостью остался бы правильным, если бы это исключение никогда не вызывалось. – dasblinkenlight

1

Некоторые люди считают его дубликатом всех общих вопросов синхронизации. Я думаю, что это не так.Вы просите о очень специфическом созвездии, и в этом смысле ваше решение «ОК».

Основываясь на том, что вы описали, фактическая цель, кажется, ясно: Вы хотите быстро и одновременно перебирать объекты для вызова метода update и избежать каких-либо накладных расходов синхронизации, которые могут быть подразумеваемой с помощью Collections#synchronizedList или аналогичные подходы.

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

Таким образом, добавление и удаление элементов является исключение, по сравнению с обычными операциями ;-)

И (как dasblinkenlight уже указывалось в his answer) для такой установки, решение ловли и игнорирование ConcurrentModificationException разумно, , но вы должны знать о последствиях.

Это может случиться так, что update метод некоторые сущности называется, а затем поручительства цикла из-за ConcurrentModificationException. Вы должны быть абсолютно уверены, что это не имеет нежелательных побочных эффектов. В зависимости от того, что на самом деле делает update, это может привести к тому, что некоторые объекты будут двигаться быстрее по экрану, а другие - вообще не перемещаться, потому что их вызовы update были упущены из-за нескольких ConcurrentModificationExceptions. Это может быть особенно проблематично, если добавление и удаление объектов - это не операция, которая случается редко: если один поток постоянно добавляет или удаляет элементы, то последние элементы списка могут никогда вообще не получать вызов update.


Если вы хотите какой-то «оправдание, например,» Я впервые столкнулся этот шаблон в JUNG Graph Library, например, в SpringLayout классе и другие. Когда я впервые увидел это, я немного сглотнул, потому что на первый взгляд он выглядит ужасно хамским и опасным. Но здесь оправдание одно и то же: процесс должен быть как можно быстрее, и изменения структуры графа (которые могут вызвать исключение) редки. Обратите внимание, что ребята JUNG на самом деле делают рекурсивный вызов соответствующему методу, когда происходит ConcurrentModificationException - просто потому, что они не всегда могут предполагать, что метод будет вызываться постоянно другим потоком. В свою очередь, может иметь неприятные побочные эффекты: если другая нить выполняет постоянные изменения, а ConcurrentModificationException выбрасывается каждый раз, когда метод называется, то это закончится StackOverflowError ... Но это не так. кейс для вас, к счастью.

+0

Это очень хороший момент и хорошо объясняет мои намерения. К вашему последнему замечанию об обновлении некоторых объектов. Я не уверен, что это будет так. Когда возникает ошибка, в нем конкретно рассматривается строка 'for ...', а не строка, вызывающая метод обновления. Это заставляет меня думать, что он понимает, что он вообще не может перебирать список, поскольку он используется другим потоком и просто не выполняет цикл вообще. Однако я не уверен в этой идее, это было просто мое впечатление. – zephyr

+0

@zephyr Дело в том, что вы не знаете, когда * будет исключено исключение. Если список содержит 10 элементов, то цикл 'for' может обрабатывать первые 5 элементов. Затем другой поток изменяет список, а цикл 'for' вырывается с помощью' C.M.Exception'. В следующий раз, когда вызывается 'update', он может снова обновлять только первые 5 элементов и снова прерываться с помощью исключения ... – Marco13