2014-12-03 3 views
1

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

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

я работала достаточно долго на одном классе, чтобы сделать это легко, и теперь я столкнулся с проблемой:

ArrayList<PVector> vecs = new ArrayList<PVector>(); 

for (int i = 0; i < 10; i++) { 
    vecs.add(new PVector()); 
} 

List<PVector> subList = vecs.subList(0, 5); 

for (int i = 0; i < 10; i++) { 
    vecs.add(new PVector()); 
} 

// ConcurrentModificationException 
for (int i = 0; i < subList.size(); i++) { 

} 

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

Второй. Что было бы хорошим способом справиться с этим? Я мог бы сделать большой массив действительно большим, так что менее вероятно, что мне нужно добавить элементы после того, как я уже сделал сублист, но мне интересно, есть ли какие-либо другие хорошие способы борьбы с ним.

Вот класс, я сделал, что должен был сделать его легким для меня, но делает мою жизнь трудно прямо сейчас :)

public class ListDivisor<T> { 

    List<T> list; 

    int subListStartIndex = 0; 
    int currentGetIndex = 0; 

    InstanceHelper instanceHelper; 


    public ListDivisor(List<T> list, InstanceHelper<T> instanceHelper) { 
     this.list = list; 
     this.instanceHelper = instanceHelper; 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public void reset() { 
     subListStartIndex = 0; 
     currentGetIndex = 0; 

     if (instanceHelper.doResetInstances()) { 
      for (T obj : list) { 
       instanceHelper.resetInstance(obj); 
      } 
     } 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public List<T> getSubList(int size) { 

     int fromIndex = subListStartIndex; // inclusive 
     int toIndex = fromIndex + size; // exclusive 

     for (int i = list.size(); i < toIndex; i++) { 
      list.add((T) instanceHelper.createInstance()); 
     } 

     subListStartIndex = toIndex; 
     currentGetIndex = toIndex; 

     return list.subList(fromIndex, toIndex); 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 

    /** 
    * Returns a subList starting where the previous subList ended till 
    * the latest object added till then. 
    * 
    * @return 
    */ 
    public List<T> getSubList() { 
     return getSubList(currentGetIndex-subListStartIndex); 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public T getNext() { 

     if (currentGetIndex >= list.size()) { 
      list.add((T)instanceHelper.createInstance()); 
     } 

     return list.get(currentGetIndex++); 

    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public void clear() { 
     list.clear(); 
     reset(); 
    } 


    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 

    public interface InstanceHelper<T> { 
     public T createInstance(); 
     public boolean doResetInstances(); 
     public void resetInstance(T obj); 
    } 

} 

Это небольшой пример того, как использовать класс:

ListDivisor<PVector> vectorsDivisor = new ListDivisor<PVector>(
    new ArrayList<PVector>(), 
    new InstanceHelper<PVector>() { 
      //@Override 
      public PVector createInstance() { 
       return new PVector(); 
      } 

      //@Override 
      public boolean doResetInstances() { 
       return true; 
      } 

      //@Override 
      public void resetInstance(PVector v) { 
       v.set(0,0,0);    
      } 
    }); 

    PVector v = vectorsDivisor.getNext(); 
    v.set(1, 1, 1); 

    v = vectorsDivisor.getNext(); 
    v.set(2, 2, 2); 

    v = vectorsDivisor.getNext(); 
    v.set(3, 3, 3); 

    subList = vectorsDivisor.getSubList(); 


    println("subList size: "+subList.size()); 
+0

Вы знаете, вы могли бы просто сделать копию подписок, так что изменение оригинала не вызовет проблемы. –

+0

Итак, ваша проблема в том, что использование подписок, возвращаемых 'getSubList'' listDivisor', вызывает «ConcurrentModificationException»? – Edward

+0

@Edward - Yep - см. Http://stackoverflow.com/questions/27262602/possible-reason-for-concurrentmodificationexception –

ответ

2

по Javadocs для ArrayList.sublist:

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

В реализации ArrayList в JVM, которую вы используете , то «неопределенные» семантика, казалось бы, что он бросает ConcurrentModificationException.


Во-первых, это плохой дизайн Java, что делает кидает одновременное изменение, если я использую добавить (Object)?

Nope. Это не «плохой дизайн».

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

Это не должно влиять на какой-либо сублист, который у меня уже есть, поскольку он добавляет в конец вправо?

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

(Поэтому мой "по уважительной причине" комментарий выше.)

Второй. Что было бы хорошим способом справиться с этим?

Трудно сообщить об этом, не понимая, что именно на самом деле должен делать ваш код. Но возникают некоторые общие альтернативы:

  • Измените свой алгоритм так, чтобы вы не использовали метод List.sublist; например передайте первый и последний индексы ваших условных подписок в качестве параметров.

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

  • Смените код, чтобы использовать Queue или Deque вместо List.

  • Внесите свой собственный класс List (на основе ArrayList), который не бросает CME в этом безопасном сценарии.

1

documentation для метода Подсписок Список_массивов указывает, что подсписки следует использовать только тогда, когда структура списка подложки не изменяется:

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

Хотя вы правы, что добавление к концу заднего хода список не должен вызывать каких-либо неожиданных действий в подсписках, Java в этом случае пессимистична и бросает ConcurrentModificationException, если вы используете подсписчик каким-либо образом после изменения списка поддержки. Это похоже на отказоустойчивое поведение итераторов списков, что также объясняется в the ArrayList docs, и он считает, что лучше провалиться чисто, а не ошибаться, что одновременные изменения «безопасны» и заканчиваются неожиданным поведением.

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

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

С другой стороны, если вам действительно нужен List -совместимого вид подсписков для каждого контура задач, вы можете реализовать свой собственный класс Подсписка, который не выбрасывает исключение, если список его поддержки получает add() Эды, а затем написать небольшие подкласс ArrayList, который переопределяет subList(), чтобы вернуть это вместо этого. Исходный код для Java ArrayList и SubList - available online, поэтому вы можете обратиться к нему, если хотите, чтобы ваш SubList выполнял все действия Java.

+0

Документация Java не содержит четких сведений о * структурных * изменениях. Хотя он включает пример для 'clear()' родительских элементов через сам под-список. –

+0

@ S.D. Я в замешательстве - не входит ли в ту часть документации, которую я цитирую, структурные изменения? Кроме того, пример 'clear()' изменяет под-список, в то время как OP задает вопрос о параллельных модификациях родителя. – Edward

+0

Эта строка в документах 'undefined, если список поддержки (т. Е. Этот список) структурно изменен каким-либо образом, кроме как через возвращенный список. 'Запутан. Специально: 'любым способом, кроме как через возвращенный список'. Поэтому я думаю, нам нужно выяснить, могут ли операции над суб-списком изменять только внутренние ссылки подписок или родительский список. –