6

Torna alla concorrenza. Ormai è chiaro che per il double checked locking funzionare la variabile deve essere dichiarata come volatile. Ma allora cosa succederebbe se il doppio controllo fosse usato come sotto.Bloccaggio con doppia verifica con HashMap normale

class Test<A, B> { 

    private final Map<A, B> map = new HashMap<>(); 

    public B fetch(A key, Function<A, B> loader) { 
     B value = map.get(key); 
     if (value == null) { 
      synchronized (this) { 
       value = map.get(key); 
       if (value == null) { 
        value = loader.apply(key); 
        map.put(key, value); 
       } 
      } 
     } 
     return value; 
    } 

} 

Perché in realtà deve essere un ConcurrentHashMap e non un normale HashMap? Tutte le modifiche della mappa vengono eseguite all'interno del blocco synchronized e il codice non utilizza gli iteratori, quindi tecnicamente non dovrebbero esserci problemi di "modifica simultanea".

Si prega di evitare che suggerisce l'uso di putIfAbsent/computeIfAbsent come sto chiedendo circa il concetto di e non l'uso di API :) a meno di utilizzare questa API contribuisce a HashMap vs ConcurrentHashMap soggetto.

Aggiornamento 2016-12-30

Questa domanda è stato risposto con un commento qui sotto da Holger "HashMap.get non modifica la struttura, ma la vostra invocazione del put fa. Poiché non v'è un'invocazione di get fuori del blocco sincronizzato, può vedere uno stato incompleto di un'operazione put che si verifica contemporaneamente. " Grazie!

+1

Poiché la mappa verrà ridimensionata dopo alcuni passaggi, interromperà sicuramente le chiamate sbloccate. –

+5

putIfAbsent/computeIfAbsent sono atomici e non solo per comodità –

+0

Thomas, potresti elaborare "break get calls" per favore. Ho trovato solo riferimenti espliciti a put ed iteratori simultanei nei documenti e durante la ricerca di "accesso simultaneo ad hashmap" –

risposta

15

Questa domanda è confusa su così tanti aspetti che è difficile rispondere.

Se questo codice viene sempre chiamato da un singolo thread, lo si rende troppo complicato; non hai bisogno di alcuna sincronizzazione. Ma chiaramente questa non è la tua intenzione.

Quindi, più thread chiamano il metodo di recupero, che delega a HashMap.get() senza alcuna sincronizzazione. HashMap non è thread-safe. Bam, fine della storia. Non importa nemmeno se stai cercando di simulare il blocco a doppio controllo; la realtà è che chiamare get() e put() su una mappa manipolerà le strutture interne di dati mutabili di HashMap, senza sincronizzazione coerente su tutti i percorsi di codice, e dal momento che puoi chiamarli contemporaneamente da più thread, sei già morto.

(Inoltre, probabilmente pensate che HashMap.get() sia una pura operazione di lettura, ma anche questo è sbagliato.Che cosa succede se HashMap è in realtà una LinkedHashMap (che è una sottoclasse di HashMap.) LinkedHashMap.get() aggiornerà l'ordine di accesso , che comporta la scrittura di strutture di dati interne - qui, contemporaneamente senza sincronizzazione. Ma anche se get() non sta scrivendo, il codice qui è ancora rotto)

Regola pratica: quando pensi di avere un intelligente trucco che ti permette di evitare la sincronizzazione, quasi sicuramente ti stai sbagliando.

+0

il secondo valore di controllo == null può essere scartato dal compilatore? –

+0

Grazie per la risposta. Non solo spiega il problema, ma fornisce anche una preziosa informazione sui dettagli di implementazione del metodo 'get()'. – AlexR

+0

Brian, sono d'accordo con la prima metà della risposta. Il contratto è che HashMap non è sincronizzato e dovrebbe essere usato come tale. La seconda metà è falsa perché la domanda elenca le classi concrete. E poiché hai menzionato l'implementazione, in base al sorgente Java fornito, HashMap.get non modifica la struttura dell'attuale implementazione Java7/8. E ancora, sono d'accordo con la prima metà e l'avrei accettata come risposta se ci fosse un riferimento autorevole che copre HashMap.get –