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);
}
`Beobachter``` in` `s.removeObserver``` zu sperren, aber weil der Hauptthread bereits Sperren für`
Beobachter``` hat Ich kann das Schloss nicht bekommen. Andererseits wird es zu einer toten Sperre, weil es auf die Verarbeitung des Threads im Hintergrund des Hauptthreads wartet.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);
}
//Kein Fehler, aber keine Reichweite hier
System.out.println("finish");
}
}
// 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);
}
Ein besserer Weg ist die Verwendung von "CopyOnWriteArrayList" anstelle des synchronisierten Blocks.
// 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);
}
Die Verarbeitung in synchronisierten Blöcken sollte so klein wie möglich sein. Wenn Sie einen Prozess benötigen, der lange dauert, sollten Sie versuchen, den Prozess außerhalb des synchronisierten Blocks zu verschieben, ohne die Item78-Anleitung zu beschädigen.
Die Kosten für eine übermäßige Synchronisierung sind nicht die CPU-Zeit, um die Sperre aufzuheben, sondern der Verlust der Möglichkeit zur Parallelisierung und der Verarbeitung, um die Konsistenz mit dem Speicher über alle Kerne hinweg sicherzustellen. Ein weiteres Problem besteht darin, dass die VM die Codeausführung aufgrund übermäßiger Synchronisierung nicht optimiert.
Es gibt zwei Möglichkeiten, veränderbare Klassen zu schreiben.
Schreiben Sie keinen Code für die Synchronisation, sondern überlassen Sie die Synchronisation dem Benutzer.
Schreiben Sie eine thread-sichere (Item82) Klasse und halten Sie sie intern synchron.
`StringBuffer``` wird meistens nur von einem einzelnen Thread verwendet, ist jedoch intern synchronisiert. Daher wurde es durch "StringBuilder" ersetzt, ein Mechanismus, der nicht synchronisiert.
java.util.Random```, das Zufallszahlen erstellt, wurde aus demselben Grund durch
`` java.util.concurrent.ThreadLocalRandom``` ersetzt.
Es gibt verschiedene Techniken zum Erstellen von Klassen, die intern synchronisiert werden. Sie werden in diesem Buch jedoch nicht erwähnt.
Wenn eine Methode ein statisches Feld ändert und von mehreren Threads aufgerufen werden kann, muss der Zugriff auf dieses statische Feld intern synchronisiert werden. Dies liegt daran, dass es nicht extern synchronisiert werden kann.