2012-04-20 21 views
10

Desidero assicurarmi di comprendere correttamente il comportamento degli 'oggetti efficacemente immutabili' in base al modello di memoria Java.Oggetto effettivamente immutabile

Diciamo che abbiamo una classe mutevole, che vogliamo pubblicare come un efficace immutabile:

class Outworld { 
    // This MAY be accessed by multiple threads 
    public static volatile MutableLong published; 
} 

// This class is mutable 
class MutableLong { 
    private long value; 

    public MutableLong(long value) { 
    this.value = value; 
    } 

    public void increment() { 
    value++; 
    } 

    public long get() { 
    return value; 
    } 
} 

Abbiamo effettuare le seguenti operazioni:

// Create a mutable object and modify it 
MutableLong val = new MutableLong(1); 
val.increment(); 
val.increment(); 
// No more modifications 
// UPDATED: Let's say for this example we are completely sure 
//   that no one will ever call increment() since now 

// Publish it safely and consider Effectively Immutable 
Outworld.published = val; 

La domanda è: Il modello di memoria Java garantisce che tutte le discussioni DEVONO avere Outworld.published.get() == 3?

Secondo Java Concurrency In Practice questo dovrebbe essere vero, ma per favore correggimi se sbaglio.

3.5.3. Idiomi di pubblicazione sicuri

Per pubblicare un oggetto in modo sicuro, sia il riferimento all'oggetto che lo stato dell'oggetto devono essere resi visibili ad altri thread contemporaneamente. Un oggetto correttamente costruito può essere tranquillamente pubblicato da:
- Inizializzazione di un riferimento a un oggetto da un inizializzatore statico;
- Memorizzare un riferimento ad esso in un campo volatile o AtomicReference;
- Memorizzare un riferimento ad esso in un campo finale di un oggetto costruito correttamente; oppure
- Memorizzare un riferimento ad esso in un campo adeguatamente protetto da un lucchetto.

3.5.4. Oggetti efficacemente immutabili

Gli oggetti effettivamente immutabili, pubblicati con sicurezza, possono essere utilizzati in modo sicuro tramite qualsiasi thread senza sincronizzazione aggiuntiva.

+0

Si prega di mostrare [* static initializer *] (http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) che stabilisce lo stato dell'oggetto prima che un riferimento ad esso sia reso visibile . – trashgod

risposta

5

Sì. Le operazioni di scrittura su MutableLong sono seguite da una relazione happens-before (su volatile) prima della lettura.

(E 'possibile che un thread legge Outworld.published e passa a un altro thread non sicuro. In teoria, che potrebbe vedere lo stato in precedenza. In pratica, non vedo che succede.)

+1

Il tuo commento (2 ° paragrafo) sembra incompatibile con la tua risposta (1 ° paragrafo) e l'accaduto prima collegato alla lettura volatile. Puoi elaborare? – assylias

+2

Supponiamo che il thread T1 safe sia pubblicato su T2. C'è un * succede-prima * da T1 a T2. Ma se lo stesso oggetto è pubblicato in modo non sicuro da T2 a T3, allora non c'è * succede-prima * da T2 a T3 e quindi anche * non avviene prima di * da T1 a T3. –

2

Il domanda è: Java Memory Model garantisce che tutti i thread DEVONO avere Outworld.published.get() == 3?

La risposta breve è no. Perché altri thread potrebbero accedere a Outworld.published prima che sia stato letto.

Dopo il momento in cui Outworld.published = val; era stata eseguita, a condizione che nessun altre modifiche eseguite con la val - sì - sempre essere 3.

Ma se qualsiasi thread esegue val.increment, il suo valore potrebbe essere diverso per altri thread.

+0

Sì, sto parlando di situazione in cui possiamo considerare questo oggetto come efficacemente immutabile; noi siamo completamente sicuri che nessun altro thread chiamerà mai "increment()" su di esso. Ho aggiornato un esempio per essere più specifico. –

4

C'è un paio di condizioni che devono essere soddisfatte per il modello di memoria Java per garantire che Outworld.published.get() == 3:

  • il frammento di codice che hai postato che crea e incrementa il MutableLong, quindi imposta il campo Outworld.published, deve accadere con visibilità tra i passaggi. Un modo per ottenere questo risultato è quello di far funzionare tutto il codice in un singolo thread, garantendo "semantica come se seriale" ". Presumo che sia quello che intendevi, ma ho pensato che valesse la pena sottolinearlo.
  • letture di Outworld.published devono avere succede dopo la semantica dall'assegnazione. Un esempio di questo potrebbe essere lo stesso thread eseguire Outworld.published = val;quindi avviare altri thread in grado di leggere il valore. Ciò garantirebbe la semantica "come seriale", impedendo il riordino delle letture prima dell'assegnazione.

Se è possibile fornire tali garanzie, il JMM garantirà tutti i thread vedere Outworld.published.get() == 3.


Tuttavia, se siete interessati a consigli di progettazione di programmi generali in questo settore, continuate a leggere.

Per la garanzia che nessun altro thread mai vedono un valore diverso per Outworld.published.get(), voi (lo sviluppatore) devono garantire che il vostro programma non modifica il valore in alcun modo. O eseguendo successivamente Outworld.published = differentVal; o Outworld.published.increment();. Mentre questo è possibile garantire, può essere molto più facile se si progetta il codice per evitare sia l'oggetto mutevole, e l'utilizzo di un campo non finale statica come punto globale di accesso per più thread:

  • invece di pubblicazione MutableLong, copia i valori pertinenti in una nuova istanza di una classe diversa, il cui stato non può essere modificato. Ad esempio: introdurre la classe ImmutableLong, che assegna value a un campo final in fase di costruzione e non dispone di un metodo increment().
  • anziché più thread che accedono a un campo non finale statico, passare l'oggetto come parametro alle implementazioni Callable/Runnable. Ciò impedirà la possibilità che un thread non autorizzato riassegni il valore e interferisca con gli altri, ed è più facile ragionare rispetto alla riassegnazione del campo statico. (Certamente, se hai a che fare con codice legacy, è più facile a dirsi che a farsi).
+0

Sono completamente d'accordo con quello che dici. La sincronizzazione è troppo facile da sbagliare. Lo eviterei ad ogni costo. La mutevolezza condivisa non è un modo sicuro di progettare un algoritmo o un programma. Penso che ogni programmatore dovrebbe dedicare un po 'di tempo allo studio dei concetti di programmazione funzionale e applicarli al mondo degli oo. Ne vale sicuramente la pena. – bennidi