Francesco93 ha scritto:
ad esempio ho un metodo add al cui interno c'è un blocco synchronized del tipo:
public void add(){
synchronized(this){
c++;
}
}
quel synchronized sta a dire che io, thread, che sono in questo blocco, ottengo il lock su questo monitor, dove this è il monitor, giusto?
Sì, giusto. Ogni oggetto, di qualunque classe (anche di array) possiede quello che la documentazione tecnica ufficiale chiama "monitor" (ci sono ragioni storiche, se non sbaglio, riguardo questo nome ma dovrei andare a rileggere).
Solamente un thread per volta può acquisire il monitor di un oggetto. Questo permette di rendere "atomiche" una serie di istruzioni rispetto alla concorrenza tra i thread. In sostanza impone una "serializzazione" tra i thread per la esecuzione di quel blocco di codice sincronizzato.
Francesco93 ha scritto:
ma cosa significa invece scrivere synchronized(object) dove object sia diverso da this?
Cambia solo rispetto a cosa è "sincronizzato" un blocco di codice. Generalmente si mette synchronized sui metodi di istanza, quindi il monitor è sostanzialmente il
this. Questo vuol anche dire che l'oggetto di lock è "pubblico", chiunque ne ha il reference può acquisire quel monitor.
Ci sono casi/scenari in cui è necessario che il monitor sia nascosto, per varie ragioni. E allora si fa in genere una cosa tipo:
public class UnaClasse {
private final Object lockObj = new Object();
public void metodoA() {
synchronized (lockObj) {
// .......
}
}
public void metodoB() {
synchronized (lockObj) {
// .......
}
}
}
In questo modo l'oggetto di lock non è più il
this ma quel lockObj. Che essendo private è quindi nascosto. Chi ha il riferimento ad un oggetto UnaClasse non può più pensare di fare
synchronized (oggUnaClasse) {
oggUnaClasse.metodoA();
}
perché non impedirebbe ad un altro thread di eseguire nel frattempo direttamente metodoB() su quello stesso oggetto.
Non sto a dilungarmi, ripeto che ci possono essere ragioni davvero valide per nascondere il monitor.
Quando vedi synchronized in un codice innanzitutto devi determinare quale è l'oggetto di lock, comprendere il suo scope (chi/dove lo può usare) e a quel punto pensare semplicemente che quello farà da "serializzatore" degli accessi.
Stesso oggetto di lock = mutua esclusione. Tutto qui.
Francesco93 ha scritto:
vi posto questo codice:
tutto funziona bene usando MyCounter in tale maniera, ma non ho ben capito come...
Il codice che hai postato è un po' "contorto", per la struttura ma anche per le denominazioni (una classe Counter e una variabile ... Counter).
Comunque tieni presente che ++ e -- NON sono "atomici". Sono 3 operazioni distinte fatte dalla JVM: leggi, incrementa, scrivi. Quindi in un'ottica di concorrenza tra thread, ++/-- vanno sempre fatti in modo sincronizzato, altrimenti potresti avere una
race-condition (due thread leggono e incrementano lo stesso valore).
Nel codice comunque una cosa ti deve risultare chiara. L'oggetto di lock è una istanza di MyRunnable.Counter. Ce n'è uno solo, siccome è tenuto in un campo static, quindi anche avendo N istanze di MyRunnable il lock è sempre lo stesso. Pertanto tutto il codice in quel blocco synchronized è assolutamente in mutua esclusione rispetto a tutte le istanze di MyRunnable.
Francesco93 ha scritto:
infatti se provo ad usare un int normale ad esempio e scrivere synchronized((Integer)C) non funziona...
Se C è un int, allora quello è un auto-boxing (Java 5 in poi). La implementazione dell'auto-boxing mette in atto una forma di caching degli oggetti ma solo per un certo range molto ristretto di valori.
Se C vale 10, ad ogni passaggio in quel codice, l'auto-boxing ti fornisce lo stesso medesimo oggetto Integer. Se C valesse 1000, allora no, crea sempre
nuovi oggetti Integer. Oggetti di lock diversi ... niente mutua esclusione. Stop.
Quindi no, non ha senso fare una cosa del genere, non è proprio da fare.