2011-11-22 17 views
5

Ho una classe semplice che esegue alcuni calcoli nel proprio thread e riporta i risultati all'ascoltatore.java: devo dichiarare volatile la mia variabile membro listener condivisa?

class Calculator extends Thread { 
    protected Listener listener; 

    public void setListener(Listener l) { 
     listener = l; 
    } 

    public void run() { 
     while (running) { 
      ... do something ... 

      Listener l = listener; 

      if (l != null) { 
       l.onEvent(...); 
      } 
     } 
    } 
} 

In qualsiasi momento, l'utente può chiamare setListener (null) se non vuole alcun evento per un certo periodo di tempo. Così, nel corsa function(), creo una copia di chi ascolta, quindi non posso correre in un NullPointerException che potrebbe accadere se l'ascoltatore è impostato su null dopo la ! = Null condizione di controllo riuscito . Nel mio caso, credo che questa sia un'alternativa corretta per la sincronizzazione.

La mia domanda è: devo dichiarare qui la variabile membro del listener come volatile? Ho letto molto su volatile, ma tutti gli esempi sembrano mirare ai tipi di dati di base (booleano, int, ...) e non agli oggetti. Quindi, quindi, non sono sicuro se gli oggetti dovrebbero/potrebbero essere dichiarati volatili. Credo di doverlo dichiarare come volatile, quindi il thread ha sempre l'ultima versione della variabile membro, ma non ne sono sicuro.

Grazie!

+0

Proprio come un suggerimento - non rispondere veramente alla tua domanda lo ammetto - ma funzionerebbe meglio mettere un flag di tipo "abilitato" sul listener, e quindi piuttosto che avere il client imposta il Listener null o not-null , averlo impostatoEnabled (true) o setEnabled (false)? Questo ti aiuta a gestire un NPE inavvertito e ti impedisce anche di dover periodicamente istanziare nuovi oggetti Listener. –

+0

@Japer D., Questa è davvero una soluzione davvero brutta che hai. – mre

+0

@JimKiley Sono d'accordo con te. Tuttavia, stavo semplificando la mia domanda, quindi sembra un po 'strano. Ci scusiamo per questo. –

risposta

5

Sì. Per garantire che il thread Calculator visualizzi un nuovo valore, impostato da un altro thread, è necessario rendere la variabile volatile.

Tuttavia, volatile è un meccanismo di livello molto basso e raramente utilizzato nel codice client. Ti suggerisco di considerare l'utilizzo di java.util.concurrent.AtomicReference in questo scenario, il che assicura che queste cose funzionino come previsto.

+0

OK, grazie per la conferma. Guarderò AtomicReference, devo ammettere che ho ignorato il nuovo pacchetto concorrente per troppo tempo. –

+0

Hehe ... anche io, ma è perché cerco di evitare del tutto la concorrenza il più a lungo possibile :-) – aioobe

+0

good thinking :) –

3

Se si utilizza questo approccio, non è più possibile garantire che un listener non riceverà una notifica di evento dopo i ritorni setListener(null). Esecuzione potrebbe procedere come segue:

Listener l = listener; // listener != null at this point 

// setListener(null) executes here 

if (l != null) { 
    l.onEvent(...); 
} 

Se è necessario essere garantito che nessun evento saranno inviati ad un ascoltatore dopo che è stato registrato, quindi è necessario utilizzare blocchi sincronizzati. Dichiarare listener per essere volatile non aiuterà. Il codice dovrebbe invece essere:

public synchronized void setListener(Listener l) { 
    listener = l; 
} 

public void run() { 
    while (running) { 
     ... do something ... 

     synchronized (this) { 
      if (listener != null) { 
       listener.onEvent(...); 
      } 
     } 
    } 
} 

Se si vuole evitare la spesa di synchronized tutto il tempo, si può fare questo:

if (listener != null) { 
    synchronized (this) { 
     if (listener != null) { 
      listener.onEvent(...); 
     } 
    } 
} 

Questa corre il rischio minimo che vi mancherà un evento dopo impostazione di un listener non null. Dichiarare listener per essere volatile probabilmente risolverebbe quello.

+0

Questo è esattamente il motivo per cui copio la variabile membro. Se viene chiamato setListener (null), la variabile locale l in run() non viene modificata. In questo modo, evito il blocco di sincronizzazione. No? –

+0

@TedHopp, no, poiché copia il contenuto in una variabile locale. – aioobe

+0

@JaperD. - L'ho capito e ho completamente rivisto la mia risposta. –

Problemi correlati