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
dans `` s.removeObserver, mais parce que le thread principal a déjà verrouillé
observers```. Je ne peux pas le déverrouiller. En revanche, il devient un verrou mort car il attend le traitement du thread en arrière-plan du thread principal.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);
}
//Aucune erreur mais aucune portée ici
System.out.println("finish");
}
}
ConcurrentModificationException '' ci-dessus se produit, aucun blocage ne s'est produit. C'est parce que les verrous de langage Java sont
réentrants ''. Les verrous réentrants simplifient la programmation multithread, mais peuvent faire d'un échec de vivacité un échec de sécurité. (**? **)// 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);
}
Une meilleure façon est d'utiliser `` CopyOnWriteArrayList '' au lieu du bloc synchronisé.
// 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);
}
Le traitement en blocs synchronisés doit être aussi petit que possible. Si vous avez besoin d'un processus qui prend beaucoup de temps, vous devez essayer de déplacer le processus en dehors du bloc synchronisé sans casser le guide Item78.
Le coût d'une synchronisation excessive n'est pas le temps CPU pour prendre le verrou, mais la perte de l'opportunité de paralléliser et le traitement imposé pour assurer la cohérence avec la mémoire sur tous les cœurs. Un autre problème est que la machine virtuelle n'optimise pas l'exécution du code en raison d'une synchronisation excessive.
Il existe deux options pour écrire des classes mutables.
N'écrivez pas de code lié à la synchronisation, laissez la synchronisation à l'utilisateur.
Ecrivez une classe thread-safe (Item82) et gardez-la synchronisée en interne.
StringBuffer '' n'est principalement utilisé que par un seul thread, mais il est synchronisé en interne. Par conséquent, il a été remplacé par
StringBuilder '', qui est un mécanisme qui ne se synchronise pas.
java.util.Random, qui crée des nombres aléatoires, a été remplacé par `` `` java.util.concurrent.ThreadLocalRandom
pour la même raison.
Il existe différentes techniques pour créer des classes qui se synchronisent en interne, mais elles ne sont pas mentionnées dans ce livre.
Si une méthode modifie un champ statique et peut être appelée par plusieurs threads, l'accès à ce champ statique doit être synchronisé en interne. C'est parce qu'il ne peut pas être synchronisé en externe.