2013-08-12 10 views
8

Ho visto questo tipo di codice molto in progetti, in cui l'applicazione vuole un supporto globale dei dati, in modo da utilizzare un Singleton statica che qualsiasi thread può accedere.Qual è la visibilità della memoria delle variabili accessibili in singleton statici in Java?

public class GlobalData { 

    // Data-related code. This could be anything; I've used a simple String. 
    // 
    private String someData; 
    public String getData() { return someData; } 
    public void setData(String data) { someData = data; } 

    // Singleton code 
    // 
    private static GlobalData INSTANCE; 
    private GlobalData() {} 
    public synchronized GlobalData getInstance() { 
     if (INSTANCE == null) INSTANCE = new GlobalData(); 
     return INSTANCE; 
    } 
} 

Spero sia facile vedere cosa sta succedendo. Si può chiamare GlobalData.getInstance().getData() in qualsiasi momento su qualsiasi thread. Se due thread chiamano setData() con valori diversi, anche se non puoi garantire quale "vince", non sono preoccupato.

Ma thread-sicurezza non è la mia preoccupazione qui. Quello che mi preoccupa è la visibilità della memoria. Ogni volta che c'è una barriera di memoria in Java, la memoria cache viene sincronizzata tra i thread corrispondenti. Una barriera di memoria avviene quando passa attraverso sincronizzazioni, l'accesso a variabili volatili, ecc

Immaginate il seguente scenario accadendo in ordine cronologico:

// Thread 1 
GlobalData d = GlobalData.getInstance(); 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Non è possibile che l'ultimo valore di value in filo 1 può essere ancora "one"? Il motivo è, filo 2 non ha mai chiamato i metodi sincronizzati dopo aver chiamato d.setData("two") quindi non c'era mai una barriera di memoria? Si noti che la barriera di memoria in questo caso si verifica ogni volta che viene chiamato getInstance() perché è sincronizzato.

+1

Si dice 'Si può chiamare GlobalData.getInstance(). GetData() in qualsiasi momento su qualsiasi thread. È sicuro per i thread, quindi non sono preoccupato per questo, ma in realtà non è thread-safe esattamente a causa dello scenario che descrivi. La sicurezza del thread non riguarda solo il vedere un oggetto in uno stato incoerente. Riguarda anche la visualizzazione di dati obsoleti. – yair

+0

@yair Sì, ma stavo davvero cercando di esaminare l'aspetto di visibilità della memoria rispetto agli stati incoerenti basati sull'esecuzione sincrona. –

risposta

1

Non è possibile che l'ultimo valore di valore nella filettatura 1 possa essere ancora "uno"?

Sì, lo è. Il modello di memoria java si basa sulle relazioni precedenti (hb). Nel tuo caso, hai solo getInstance uscita accade-prima successiva getInstance dell'entrata, a causa della parola chiave sincronizzato.

Quindi, se prendiamo il vostro esempio (supponendo che l'interlacciamento filo è in questo ordine):

// Thread 1 
GlobalData d = GlobalData.getInstance(); //S1 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); //S2 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Si hanno S1 hb S2. Se hai chiamato d.getData() da Thread2 dopo S2, vedresti "uno". Ma l'ultima lettura di d non è garantita per vedere "due".

2

si è assolutamente corretto.

Non v'è alcuna garanzia che scrive in una Thread sarà visibile è un altro.

Per fornire questa garanzia si avrebbe bisogno di utilizzare la parola chiave volatile:

private volatile String someData; 

Tra l'altro è possibile sfruttare il programma di caricamento classe Java per filo fornitore di sicurezza pigro init del Singleton come documentato here. Ciò evita la parola chiave synchronized e pertanto ti risparmia un blocco.

Vale la pena notare che l'attuale best practice accettata consiste nell'utilizzare uno enum per l'archiviazione dei dati singleton in Java.

2

Corretto, è possibile che il thread 1 visualizzi ancora il valore come "uno" poiché non si è verificato alcun evento di sincronizzazione della memoria e non si verifica alcun risultato prima della relazione tra thread 1 e thread 2 (vedere la sezione 17.4.5 of the JLS).

Se someData era volatile, il thread 1 vedrebbe il valore come "due" (supponendo che il thread 2 sia stato completato prima che il thread 1 abbia recuperato il valore).

Infine, e fuori tema, l'implementazione del singleton è leggermente meno ideale poiché si sincronizza su ogni accesso. In genere è preferibile utilizzare un enum per implementare un singleton o, per lo meno, assegnare l'istanza in un inizializzatore statico in modo che non sia richiesta alcuna chiamata al costruttore nel metodo getInstance.

Problemi correlati