Se ho due Thread A e B entrambi in esecuzione, A crea un Point - oggetto tipicamente mutabile - di coordinate (100, 75) e lo passa a B tout court il punto che B riceve può essere un qualsiasi valore tra un punto di coordinate (0,0), un punto di coordinate (100,0) e un punto (0,75). Questi sono i valori che B può legittimamente attendersi.
La sincronizzazione ci dice che se ho un monitor M e il Thread A assegna al punto le sue coordinate sincronizzandosi su M una successiva lettura del punto da parte di un Thread B sincronizzato su M legge necessariamente i valori precedentemente assegnati da A.
Nota bene: precedentemente. La sincronizzazione non è perpetua. Abbiamo un punto P. C'è un metodo che assegna delle coordinate a P sincronizzandosi su P stesso e poi delega la lettura di P all'EDT. Una cosa tipo:
Codice:
private final Point p = new Point();//final = p non è mai null
public void set(int x, int y) {
synchronized(p) { p.x = x; p.y = y; }
EventQueue.invokeLater(new Runnable() {
public void run() {
synchronized(p) {
leggi i valori p.x e p.y;
}
}
});
}
Qui ho la garanzia che l'aggiornamento dei valori di p è visibile all'EDT ma non ho una garanzia di ordine. Il codice garantisce che se il Thread A dice:
set(1, 1);
set(2, 2);
set(3, 3);
l'edt può vedere una qualsiasi tripla composta dai punti ( (1,1), (2,2), (3,3) ) e non vedrà mai (0,1) o (0,0) o (1,0) o (0,2) o (3,1).
Vale a dire che non esiste una race condition riguardante i valori del punto ma esiste una race condition tra le invocazioni di set da parte del Thread A e le letture dell'EDT.
Per evitare anche la seconda race condition occorre generare un nuovo punto ad ogni invocazione di set e passare quel punto all'EDT come messaggio: così facendo ogni invocazione di set genera un nuovo punto e l'eventuale invocazione plurima di set da parte del Thread A non lede la lettura dei valori successivamente impostati da parte dell'EDT.
Codice:
private final Point p = new Point();
public synchronized void set(int x, int y) {
p.x = x;
p.y = y;
final Point message = new Point();
synchronized(message) {
message.x = x;
message.y = y;
}
EventQueue.invokeLater(new Runnable() {
public void run() {
synchronized(message) {
leggi message.x e message.y
}
}
}
}
Rispetto a prima qui c'è un synchronized in più perchè "p" è supposto essere un campo mentre prima non c'erano campi. Cosa succede se un Thread invoca tre volte il metodo set adesso?
Succede che quel Thread crea tre punti diversi - uno ad ogni invocazione - ed ognuno di quei punti è passato all'edt per una lettura sincronizzata. Ergo ora se il Thread A dice:
set(1, 1);
set(2, 2);
set(3, 3);
l'Edt legge necessariamente:
(1,1)
(2,2)
(3,3)
Capita perchè rispetto ad A le tre invocazioni successive di set sono necessariamente ordinate e in più ognuna di queste crea un nuovo punto - e non agisce su un unico punto come prima.
Ma anche qui c'è race condition se anzichè parlare di un solo Thread A iniziamo ad avere due Thread, A e B, che invocano set. Qui occorrerà stabilire se esista un ordine necessario tra le invocazioni di set da parte di A e B e in caso affermativo si procederà a garantire tale ordine.