В Java Concurrency in Practice автор приводит следующий пример не поточного безопасного класса, который за кулисами вызывает итератор для объекта set
и, если задействовано несколько потоков, это может вызвать ConcurrentModificationException
. Это понятно: один поток изменяет коллекцию, другой перебирает ее и, - boom!
Что я не понимаю, - автор говорит, что этот код можно исправить, добавив HashSet
к Collections.synchronizedSet()
. Как это решит проблему? Несмотря на то, что доступ ко всем методам будет синхронизирован и защищен одной и той же внутренней блокировкой, после получения объекта итератора нет гарантии, что другой поток не изменит коллекцию после выполнения итерации.
Цитата из книги:
Если HiddenIterator обернет HashSet синхронизированным набором, инкапсулируя синхронизацию, такого рода ошибки не возникнут.
public class HiddenIterator {
//Solution :
//If HiddenIterator wrapped the HashSet with a synchronizedSet, encapsulating the synchronization,
//this sort of error would not occur.
//@GuardedBy("this")
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) {
set.add(i);
}
public synchronized void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
/*The string concatenation gets turned by the compiler into a call to StringBuilder.append(Object),
* which in turn invokes the collection's toString method - and the implementation of toString in
* the standard collections iterates the collection and calls toString on each element to
* produce a nicely formatted representation of the collection's contents. */
System.out.println("DEBUG: added ten elements to " + set);
}
}
Если бы кто-нибудь мог помочь мне понять это, я был бы благодарен.
Вот как я думаю, это могло быть исправлено:
public class HiddenIterator {
private final Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
public void add(Integer i) {
set.add(i);
}
public void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
// synchronizing in set's intrinsic lock
synchronized(set) {
System.out.println("DEBUG: added ten elements to " + set);
}
}
}
Или, в качестве альтернативы, можно оставить ключевое слово synchronized
для методов add()
и remove()
. В этом случае мы синхронизируемся на this
. Кроме того, нам нужно добавить синхронизированный блок (снова синхронизированный на this
) в addTenThings()
, который будет содержать одну операцию - запись с неявной итерацией:
public class HiddenIterator {
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) {
set.add(i);
}
public synchronized void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
synchronized(this) {
System.out.println("DEBUG: added ten elements to " + set);
}
}
}