ArrayList - добавить одинаковые объекты (same =› equals, hashCode), Threads

У меня есть один вопрос. Что происходит, когда я пытаюсь дважды добавить «один и тот же» объект в список ArrayList. Под «тот же самый» я подразумеваю объект отдельного класса, который идентифицируется как одинаковый с помощью equals() и hashCode(). Он имеет разные значения для большинства переменных-членов и был создан, возможно, из разных потоков, но для equals() и hashCode() он "одинаков". Заменяет ли второй объект первый объект?

Кроме того, что произойдет, если два потока попытаются одновременно добавить эти объекты в список ArrayList? Это вообще возможно? Если да, то что происходит?

Благодарю вас! :-)

[EDIT] Спасибо за все ответы! Должен ли я использовать synchronizedList, а не «синхронизировать (список) {}»? --> Я прочитал документы, даже с synchronizedList, для итерации следует использовать синхронизацию (список)

[EDIT2] Может ли synchronizedList быть объявлен как переменная-член? Я пытался, но это не сработало.


person nano7    schedule 26.05.2011    source источник


Ответы (5)


Нет, ArrayList вообще не пытается обнаруживать дубликаты — у вас может быть ArrayList с одной и той же ссылкой, появляющейся несколько раз. Если вы хотите, чтобы в коллекции не было дубликатов, вам нужна реализация Set, а если вы также хотите сохранить порядок вставки, вам, вероятно, понадобится LinkedHashSet.

Обратите внимание, однако, что без блокировки ArrayList не следует не мутировать из нескольких потоков в первую очередь - это просто не предназначено для того, чтобы быть потокобезопасной коллекцией таким образом. Несколько потоков могут читать из ArrayList без синхронизации, но не изменять его. Из документов:

Обратите внимание, что эта реализация не синхронизирована. Если несколько потоков одновременно обращаются к экземпляру ArrayList и по крайней мере один из потоков изменяет список структурно, он должен быть синхронизирован извне. (Структурная модификация — это любая операция, которая добавляет или удаляет один или несколько элементов или явно изменяет размер резервного массива; простая установка значения элемента не является структурной модификацией.) Обычно это достигается путем синхронизации на некотором объекте, который естественным образом инкапсулирует список. Если такого объекта не существует, список следует «обернуть» с помощью метода Collections.synchronizedList. Лучше всего это делать во время создания, чтобы предотвратить случайный несинхронизированный доступ к списку.

Если вы хотите изменить коллекцию из нескольких потоков без блокировки, я предлагаю вам просмотреть коллекции в java.util.concurrent.

person Jon Skeet    schedule 26.05.2011

Заменяет ли второй объект первый объект?

Нет, большинство разработчиков делают явные проверки

if(!list.contains(foo)){
    list.add(foo);
}

Кроме того, что произойдет, если два потока попытаются одновременно добавить эти объекты в список ArrayList? Это вообще возможно? Если да, то что происходит?

Да, это возможно. Если несколько потоков пишут/читают из одного и того же ArrayList, используйте ключевое слово synchronized всякий раз, когда вы обращаетесь к этому списку.

public List<Foo> getFoos(){
    synchronized(list){
        return list;
    }
}

public void addFoo(Foo foo){
    synchronized(list){
        list.add(foo);
    }
}

ИЗМЕНИТЬ

Как кто-то указал, я полагаю, что проверка того, содержит ли ArrayList объект, который нужно добавить, довольно дорогая. Если вы хотите убедиться, что объект добавляется только один раз, я бы следовал приведенной ниже рекомендации по использованию LinkedHashSet. Согласно API, при попытке добавить в эту структуру данных

Добавляет указанный элемент в этот набор, если он еще не присутствует. Более формально, добавляет указанный элемент e в этот набор, если этот набор не содержит элемента e2 такого, что (e==null ? e2==null : e.equals(e2)). Если этот набор уже содержит элемент, вызов оставляет набор без изменений и возвращает false.

person mre    schedule 26.05.2011
comment
-1 Каждый вызов ArrayList.contains равен O(n). Это ужасный способ создать уникальный список. LinkedHashSet лучше для этого. - person Chris Jester-Young; 26.05.2011
comment
но при использовании LinkedHashSet доступ к нему тоже должен быть синхронизирован, верно? Тогда почему бы не использовать синхронизированный список? - person nano7; 26.05.2011
comment
@nano7, конечно, нужно синхронизировать. то, что вы изменили коллекцию, не означает, что вы изменили политику синхронизации. вы можете использовать любую синхронизированную структуру данных, которую хотите, но если вы собираетесь использовать LinkedHashSet, я бы рекомендовал использовать synchronizedSet. - person mre; 26.05.2011
comment
Как это? Set s = Collections.synchronizedSet(новый LinkedHashSet‹objecttype›()); Как объявить переменную-член этого типа? его тип LinkedHashSet‹objecttype›()? Должен ли я создать синхронизированный набор вручную и установить его как переменную-член (с его приведением)? Если объект добавляется в список один раз, этот же объект нельзя добавить снова, поэтому синхронизация добавления не нужна, не так ли? - person nano7; 26.05.2011

Это позволит просто добавить. Список не имеет ничего общего с hashCode(), equals(), а вставка не заботится о дубликате.

ArrayList не является потокобезопасным, поэтому вы можете не получить желаемого результата. у тебя может быть synchronizedList из Collections класса

person jmj    schedule 26.05.2011

ArrayList может содержать несколько ссылок на один и тот же объект (эквивалентность идентичности). Он не проверяет equals() или hashCode() при добавлении объектов.

Вы просто получите две ссылки в своем ArrayList.

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

person Tom Tresansky    schedule 26.05.2011

Если вы попытаетесь добавить один и тот же объект дважды, он будет работать, или если вы попытаетесь добавить 2 объекта с одинаковыми параметрами, он все равно будет работать. Это не лучшая практика, потому что список сложнее поддерживать.

в целом: не стоит этого делать

person RMT    schedule 26.05.2011