ConcurrentModificationException
.
In the notifyElementAdded method, iterates the observers, and in that, the added method of SetObserver is executed to make changes to the observers. An error occurred because the element could not be removed from the list during iterating.package tryAny.effectiveJava;
import java.util.HashSet;
public class SynchroTest {
public static void main(String[] args) {
ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>());
set.addObserver(new SetObserver<>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) {
s.removeObserver(this);
}
}
});
for (int i = 0; i < 100; i++) {
set.add(i);
}
}
}
package tryAny.effectiveJava;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
public void clear() {
s.clear();
}
public boolean contains(Object o) {
return s.contains(o);
}
public boolean isEmpty() {
return s.isEmpty();
}
public int size() {
return s.size();
}
public Iterator<E> iterator() {
return s.iterator();
}
public boolean add(E e) {
return s.add(e);
}
public boolean remove(Object o) {
return s.remove(o);
}
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
@Override
public boolean equals(Object o) {
return s.equals(o);
}
@Override
public int hashCode() {
return s.hashCode();
}
@Override
public String toString() {
return s.toString();
}
}
package tryAny.effectiveJava;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public class ObservableSet<E> extends ForwardingSet<E> {
public ObservableSet(Set<E> set) {
super(set);
}
private final List<SetObserver<E>> observers = new ArrayList<>();
public void addObserver(SetObserver<E> observer) {
synchronized (observers) {
observers.add(observer);
}
}
public boolean removeObserver(SetObserver<E> observer) {
synchronized (observers) {
return observers.remove(observer);
}
}
private void notifyElementAdded(E element) {
synchronized (observers) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
}
@Override
public boolean add(E element) {
boolean added = super.add(element);
if (added)
notifyElementAdded(element);
return added;
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for (E element : c)
result |= add(element); // Calls notifyElementAdded
return result;
}
}
package tryAny.effectiveJava;
@FunctionalInterface
public interface SetObserver<E> {
// Invoked when an element is added to the observable set
void added(ObservableSet<E> set, E element);
}
observers
in `` s.removeObserver, but because the main thread already has
observers``` locked. I can't unlock it. On the other hand, it becomes a deadlock because it waits for the processing of the thread in the background of the main thread.package tryAny.effectiveJava;
import java.util.HashSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchroTest2 {
public static void main(String[] args) {
ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>());
set.addObserver(new SetObserver<>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) {
ExecutorService exec = Executors.newSingleThreadExecutor();
try {
exec.submit(() -> (s.removeObserver(this))).get();
} catch (ExecutionException | InterruptedException ex) {
throw new AssertionError(ex);
} finally {
exec.shutdown();
}
}
}
});
for (int i = 0; i < 100; i++) {
set.add(i);
}
//No error but no reach here
System.out.println("finish");
}
}
ConcurrentModificationException
occurs, no deadlock has occurred. This is because Java language locks are `` `reentrant```. Reentrant locking simplifies multithreaded programming, but can make liveness failure a safety failure. (**? **)// Alien method moved outside of synchronized block - open calls
private void notifyElementAdded(E element) {
List<SetObserver<E>> snapshot = null;
synchronized(observers) {
snapshot = new ArrayList<>(observers);
}
for (SetObserver<E> observer : snapshot)
observer.added(this, element);
}
A better way is to use `` `CopyOnWriteArrayList``` instead of the synchronized block.
// Thread-safe observable set with CopyOnWriteArrayList
private final List<SetObserver<E>> observers =
new CopyOnWriteArrayList<>();
public void addObserver(SetObserver<E> observer) {
observers.add(observer);
}
public boolean removeObserver(SetObserver<E> observer)
{
return observers.remove(observer);
}
private void notifyElementAdded(E element) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
Processing in synchronized blocks should be as small as possible. If you need a process that takes a long time, you should try to move the process outside the synchronized block without breaking the Item78 guide.
The cost of excessive synchronization is not the CPU time to take the lock, but the loss of the opportunity to parallelize and the processing imposed to ensure memory consistency across all cores. Another problem is that VMs do not optimize code execution due to excessive synchronization.
There are two options for writing mutable classes.
Do not write code related to synchronization, leave the synchronization to the user.
Write a thread-safe (Item82) class and keep it internally synchronized.
StringBuffer
is mostly used only by a single thread, but it is internally synchronized. Therefore, it has been superseded by StringBuilder
, which is a mechanism that does not synchronize. `java.util.Random```, which creates random numbers, has been superseded by
`java.util.concurrent.ThreadLocalRandom``` for the same reason.
There are various techniques for creating classes that synchronize internally, but they are not mentioned in this book.
If a method modifies a static field and can be called by multiple threads, access to that static field must be synchronized internally. This is because it cannot be synchronized externally.