2015-07-03 13 views
6

fa Java garantisce che gli aggiornamenti elementi dell'array fatto da filo A prima di memorizzare il riferimento matrice in un AtomicReference sarà sempre visibile per filo B che ottiene questo riferimento?AtomicReference di matrice e la matrice elemento cambia visibilità

In altre parole, quali sono le possibili uscite di esecuzione di questo:

class References { 
    AtomicReference<String[]> refs = new AtomicReference<>(new String[]{"first"}); 

    public void add(String s) { 
     refs.updateAndGet(oldRefs -> { 
      String[] newRefs = new String[oldRefs.length + 1]; 
      System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length); 
      newRefs[oldRefs.length] = s; 
      return newRefs; 
     }); 
    } 

    public static void main(String[] args) { 
     References r = new References(); 
     new Thread(() -> r.add("second")).start(); 
     System.out.println(Arrays.toString(r.refs.get())); 
    } 
} 

può quanto sopra stampare solo sia [first] o [first, second], o è anche possibile ottenere risultati come [first, null] o [null, null]?

Javadoc per java.util.concurrent.atomic stati:

compareAndSet e tutte le altre operazioni di lettura e di aggiornamento come getAndIncrement hanno gli effetti di memoria di lettura e scrittura di variabili volatili.

che sembra non fornire alcuna garanzia per gli elementi non volatile dell'array, solo il riferimento di matrice stesso.

+0

Otterrai sempre "{" prima "," seconda "}'; 'System.arrayCopy()' non ritorna prima che abbia completato il suo compito (sarebbe altrimenti inaffidabile altrimenti!) – fge

+0

Se hai assegnato l'array a un campo 'final', avresti una tale garanzia, con' AtomicReference "Non penso che tu lo faccia. – biziclop

+0

A proposito, probabilmente userei 'CopyOnWriteArrayList', che ti dà tutte le garanzie che puoi aspettarti da una tale collezione. – biziclop

risposta

5

Una scrittura volatile su una variabile garantisce che tutto ciò che è accaduto prima si verifichi prima di una successiva lettura volatile di la stessa variabile.

Le norme pertinenti del JLS sono:

17.4.4.Sincronizzazione ordine

  • Una scrittura ad una variabile v volatile (§8.3.1.4) sincronizza-con tutte le letture successive di v da qualsiasi thread (dove "successiva" è definito secondo l'ordine di sincronizzazione).

17.4.5. Happens-before Order

Due azioni possono essere ordinate in base a una relazione precedente. Se si verifica un'azione, prima di un'altra, la prima è visibile e ordinata prima della seconda.

Se abbiamo due azioni x e y, scriviamo hb (x, y) per indicare che x accade prima di y.

  • Se xey sono azioni dello stesso thread e x viene prima di y in ordine di programma, quindi hb (x, y).
  • Se un'azione x si sincronizza con una seguente azione y, allora abbiamo anche hb (x, y).

  • Se hb (x, y) e hb (y, z), quindi hb (x, z).

Come AtomicReference garantisce che il vostro riferimento array è memorizzata solo/caricati in modo volatile (e una volta scritto, non si modificano un array esistente), questo è sufficiente a garantire la visibilità dei risultati del System.arrayCopy() (e la linea che lo segue) per tutti coloro che chiamano refs.get().

Tuttavia il costrutto ancora non è del tutto impermeabile perché chiunque ottenere il riferimento matrice attraverso refs.get() può quindi continuare a modificare elementi senza la protezione di AtomicReference.

CopyOnWriteArrayList opere molto simili a questo (si utilizza la combinazione di un ReentrantLock e un campo volatile matrice anziché un AtomicReference), tranne che viene inoltre garantito che nessuno può avere la matrice sottostante e giocare con essa in un modo non sicuro.

1

non è possibile ottenere alcun elemento nullo nell'array perché non è stato modificato gli elementi di array nel codice ma ne viene creato uno nuovo ogni volta (String[] newRefs = new String[oldRefs.length + 1];). Dato che stai memorizzando il riferimento dell'array e non hai modificato i suoi elementi, non puoi vedere elementi null. Se nel codice si ha qualcosa di simile:

refs.updateAndGet(oldRefs -> { 
     if (oldRefs.size>0) oldRefs[0]=null;//is an example just for "fun" 
     String[] newRefs = new String[oldRefs.length + 1]; 
     System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length); 
     newRefs[oldRefs.length] = s; 
     return newRefs; 
    }); 

allora si può vedere qualcosa di diverso, e alcuni utenti potrebbero vedere il valore nullo nel primo elemento

UPDATE: dal AtomicReference funziona solo per la riferimento alla matrice è possibile utilizzare AtomicReferenceArray accedere in modo sicuro agli elementi dell'array

+0

gli elementi null che stavo chiedendo non provengono dalla cancellazione di riferimenti nell'array esistente, ma dal potenziale rilascio di riferimento di matrice prima che tutti gli elementi siano impostati su valori appropriati - dopo la creazione dell'array sono nulli e rimangono tali fino all'aggiornamento ad altri valori dopo. – pdabro

+0

@pdabro quando si accede al riferimento dell'array è possibile modificare i suoi elementi senza "la protezione" del riferimento atomico; il mio esempio stava cercando di mostrare questo caso, cioè stai proteggendo il riferimento e non gli elementi. – Giovanni

1

un AtomicReference ad una matrice non è diverso da qualsiasi altro - è solo il riferimento che è atomico e quindi ha le relative barriere di memoria in atto. L'accesso all'array è come qualsiasi altro oggetto: nessuna protezione aggiuntiva.

Otterrete quindi sempre [first] o [first, second] e nessun'altra opzione mentre state creando il nuovo array, popolandolo e rimettendolo nel vecchio riferimento che è protetto da atomizzazione.

Gli array Java non sono molto adatti al ridimensionamento. Se si desidera una struttura ridimensionabile si farebbe meglio ad usare un ArrayList. Se si desidera un accesso simultaneo ad esso, utilizzare uno CopyOnWriteArrayList che è essenzialmente ciò che si sta tentando di implementare nel codice.

Problemi correlati