Perché non è una buona pratica sincronizzare su Boolean?

Il mio architetto lo dice sempre

Non sincronizzare mai su Boolean

Non sono in grado di capire il motivo e apprezzerei davvero se qualcuno potesse spiegare con un esempio il motivo per cui non è una buona pratica. Codice di esempio di riferimento

private Boolean isOn = false; private String statusMessage = "I'm off"; public void doSomeStuffAndToggleTheThing(){ // Do some stuff synchronized(isOn){ if(isOn){ isOn = false; statusMessage = "I'm off"; // Do everything else to turn the thing off } else { isOn = true; statusMessage = "I'm on"; // Do everything else to turn the thing on } } } 

Non sono in grado di capire il motivo per cui non dovremmo “mai sincronizzare su Boolean”

Dovresti sempre synchronize su un’istanza di object costante . Se si esegue la sincronizzazione su qualsiasi object che si sta assegnando (ad esempio, la modifica dell’object in un nuovo object), non è costante e thread diversi si sincronizzeranno su istanze di oggetti differenti. Poiché si stanno sincronizzando su diverse istanze di oggetti, più thread entreranno nel blocco protetto nello stesso momento e le condizioni di gara avverranno. Questa è la stessa risposta per la sincronizzazione su Long , Integer , ecc.

 // this is not final so it might reference different objects Boolean isOn; ... synchronized (isOn) { if (isOn) { // this changes the synchronized object isOn to another object // so another thread can then enter the synchronized with this thread isOn = false; 

Per peggiorare le cose (come ha sottolineato @McDowell nella sua risposta) qualsiasi Boolean creato tramite autoboxing ( isOn = true ) è lo stesso object di Boolean.TRUE (o .FALSE ) che è un singleton nel ClassLoader su tutti gli oggetti . L’object di blocco deve essere locale rispetto alla class in cui viene utilizzato altrimenti si bloccherà sullo stesso object Singleton che altre classi potrebbero bloccare in altri casi di blocco se stanno commettendo lo stesso errore.

Lo schema corretto se è necessario bloccare un booleano è definire un object di blocco private final :

 private final Object lock = new Object(); ... synchronized (lock) { ... 

O dovresti anche considerare di usare l’object AtomicBoolean , il che significa che potresti non dover assolutamente synchronize .

 private final AtomicBoolean isOn = new AtomicBoolean(false); ... // if it is set to false then set it to true, no synchronization needed if (isOn.compareAndSet(false, true)) { statusMessage = "I'm now on"; } else { // it was already on statusMessage = "I'm already on"; } 

Nel tuo caso, dal momento che sembra che sia necessario lock / distriggersrlo con i thread, sarà comunque necessario synchronize sull’object lock e impostare il booleano ed evitare la condizione di test / set race:

 synchronized (lock) { if (isOn) { isOn = false; statusMessage = "I'm off"; // Do everything else to turn the thing off } else { isOn = true; statusMessage = "I'm on"; // Do everything else to turn the thing on } } 

Infine, se ci si aspetta che statusMessage sia accessibile da altri thread, dovrebbe essere contrassegnato come volatile meno che non si statusMessage la statusMessage durante l’ statusMessage .

 private Boolean isOn = false; public void doSomeStuffAndToggleTheThing(){ synchronized(isOn){ 

Questa è una pessima idea. isOn farà riferimento allo stesso object di Boolean.FALSE che è pubblicamente disponibile. Se un altro pezzo di codice scritto male decide anche di bloccare questo object, due transazioni completamente indipendenti dovranno attendere l’una sull’altra.

I blocchi vengono eseguiti su istanze di oggetti , non sulle variabili che li fanno riferimento:

inserisci la descrizione dell'immagine qui

Penso che il tuo problema sia più sincronizzato rispetto a sync’ing su Booleans. Immagina che ogni discussione sia una strada, in cui le dichiarazioni (macchine) si susseguono. Ad un certo punto potrebbe esserci un incrocio: senza un semaforo potrebbero verificarsi collisioni. Il linguaggio Java ha un modo per descriverlo: dato che ogni object può essere un’intersezione, qualsiasi object ha un monitor associato che agisce come un semaforo. Quando si utilizza sincronizzato nel codice, si sta creando un semaforo, quindi è necessario utilizzare lo stesso per tutte le strade (thread). Quindi questo problema non è in realtà specifico per il booleano perché esistono solo due booleani, questo problema si verifica ogni volta che si sincronizza su una variabile di istanza e quindi si punta la stessa variabile a un object diverso. Quindi il tuo codice è sbagliato con Booleans, ma è ugualmente pericoloso con Integers, String e qualsiasi object se non capisci cosa sta succedendo.

Modifica: la risposta di Gray è corretta.

Quello che voglio aggiungere è: il tuo architetto ha ragione, se dalla prospettiva di Boolean è immutabile , perché sincronizzarlo? Ma il multi-thread è complesso e basato sullo scenario.