Double Check Blocco
Doppia chiusura di controllo richiede diversi passaggi tutti da completare per poter funzionare correttamente, vi manca due di loro.
Per prima cosa è necessario convertire someMap
in una variabile volatile
. Questo è così che gli altri thread vedranno le modifiche apportate ad esso quando sono fatti ma dopo che le modifiche sono state completate.
private volatile Map<String, String> someMap = null;
è necessario anche un secondo controllo per null
all'interno del blocco synchronized
per fare in modo che un altro thread non è inizializzato per voi mentre erano in attesa di entrare nella zona sincronizzato.
if (someMap == null) {
synchronized(this) {
if (someMap == null) {
non si assegna fino al momento dell'uso
Nella vostra generazione della mappa costruirlo in una variabile temporanea quindi assegnare alla fine.
Map<String, String> tmpMap = new HashMap<String, String>();
// initialize the map contents by loading some data from the database.
// possible for the map to be empty after this.
someMap = tmpMap;
}
}
}
return someMap.get(key);
Per spiegare perché è necessaria la mappa temporanea. Non appena si completa la linea someMap = new HashMap...
allora someMap
non è più nullo. Ciò significa che altre chiamate a get
lo vedranno e non tenteranno mai di inserire il blocco synchronized
.Proveranno quindi ad ottenere dalla mappa senza attendere il completamento delle chiamate al database.
Assicurandosi che l'assegnazione a someMap
sia l'ultimo passaggio all'interno del blocco sincronizzato che impedisce che ciò accada.
unmodifiableMap
Come discusso nei commenti, per la sicurezza sarebbe anche meglio per salvare i risultati in un unmodifiableMap
come future modifiche non sarebbero stati al sicuro thread. Questo non è strettamente necessario per una variabile privata che non viene mai esposta, ma è ancora più sicura per il futuro in quanto impedisce alle persone di entrare in seguito e di cambiare il codice senza rendersene conto.
someMap = Collections.unmodifiableMap(tmpMap);
Perché non utilizzare ConcurrentMap?
ConcurrentMap
rende azioni individuali (cioè putIfAbsent
) thread-safe, ma non soddisfa il requisito fondamentale qui di attendere la mappa è completamente popolato con i dati prima di consentire legge da esso.
Inoltre, in questo caso, la mappa dopo l'inizializzazione pigra non viene modificata di nuovo. ConcurrentMap
aggiungerebbe sovraccarico di sincronizzazione alle operazioni che in questo caso d'uso specifico non devono essere sincronizzate.
Perché sincronizzare su questo?
Non c'è motivo. :) Era il modo più semplice per presentare una risposta valida a questa domanda.
Sarebbe sicuramente meglio eseguire la sincronizzazione su un oggetto interno privato. Hai migliorato l'incapsulamento scambiato per l'utilizzo di memoria marginalmente aumentato e tempi di creazione dell'oggetto. Il rischio principale con la sincronizzazione su this
è che consente ad altri programmatori di accedere all'oggetto di blocco e potenzialmente provare a sincronizzarsi su di esso. Ciò causa quindi un conflitto non necessario tra i loro aggiornamenti e il tuo, quindi un oggetto di blocco interno è più sicuro.
In realtà un oggetto di blocco separato è eccessivo in molti casi. È un giudizio basato sulla complessità della tua classe e su quanto ampiamente viene utilizzato contro la semplicità del solo blocco su this
. In caso di dubbio, probabilmente dovresti usare un oggetto di blocco interno e prendere la via più sicura.
Nella classe:
private final Object lock = new Object();
Nel metodo:
synchronized(lock) {
Per quanto riguarda java.util.concurrent.locks
oggetti, non aggiungono nulla di utile in questo caso (anche se in altri casi sono molto utili). Vogliamo sempre aspettare che i dati siano disponibili, quindi il blocco sincronizzato standard ci fornisce esattamente il comportamento di cui abbiamo bisogno.
inoltre riceverete NullPointerException sulla linea 'sincronizzato (someMap)' 'dal someMap' è' null' – Idolon
In questa discussione, le persone che non si rendono conto quanto sia difficile concomitante è. –