2013-08-09 13 views
6

Ho un assistente un po 'come questo:Accesso sicuro ai thread ma veloce a una variabile "finale finale"?

class Server { 

    private WorkingThing worker; 

    public void init() { 
     runInNewThread({ 
      // this will take about a minute 
      worker = new WorkingThing(); 
     }); 
    } 

    public Response handleRequest(Request req) { 
     if (worker == null) throw new IllegalStateException("Not inited yet"); 
     return worker.work(req); 
    } 

} 

Come si può vedere, ci sono discussioni gestione delle richieste e un filo initing server. Le richieste possono arrivare prima che l'inita sia finita, quindi c'è il controllo con un IllegalStateException.

Ora, per rendere questo thread-safe (così le discussioni richiesta del gestore non si vede una stantia, null -valued, versione di worker subito dopo init), avrei dovuto fare lavoratore volatili, sincronizzare su di esso, o qualche come.

Tuttavia, dopo che l'init è stato completato, worker non cambierà mai più, quindi è effettivamente definitivo. Pertanto, sembra che qualsiasi conflitto di blocco che potrebbe verificarsi sarebbe uno spreco. Quindi, qual è la cosa più efficiente che posso fare qui?

Ora so che non importa in senso pratico (con tutto il peso della lettura di una richiesta di rete, ecc., Cosa importa una singola serratura?), Ma mi piacerebbe sapere per curiosità .

+0

utilizzando la parola chiave 'sincronizzata' costa un sacco di cicli. Penso che il modo migliore sarebbe quello di rendere statica la variabile 'worker'. In questo modo assegnerà uno spazio riservato nella memoria, e penso che i fili guarderanno sempre a quello spazio. Solo un pensiero, potrebbe non essere corretto. –

+0

L'invocazione del thread 'worker.work (req)' è sicuro? Sto solo chiedendo di assicurarmi di aver solo bisogno di bloccare i thread chiamanti fino a quando 'worker' è stato inizializzato. È corretto? –

+0

@ViktorSeifert Sì, una volta 'worker' è lì, è thread-safe. –

risposta

3

Una nota su volatile: contrassegnare una variabile volatile è più economica rispetto all'utilizzo della sincronizzazione (non coinvolge i blocchi) ed è generalmente abbastanza economica che non si noterà. In particolare, sulle architetture x86, la lettura di una variabile volatile non costa più della lettura di una variabile non volatile. Tuttavia scrivere su un volatile è più costoso e il fatto che la variabile sia volatile potrebbe impedire alcune ottimizzazioni del compilatore.

Quindi utilizzare volatile è probabilmente l'opzione che offre il miglior rapporto prestazioni/complessità nello scenario.

Non hai molte alternative. Alla fine si riduce a garantire una pubblicazione sicura del vostro lavoratore. E linguaggi pubblicazione sicuri includono:

  • inizializzare l'istanza da un initialiser statico
  • marcatura il riferimento alla istanza come ultima
  • segna il riferimento all'istanza come volatile
  • sincronizzare tutti gli accessi

Nel tuo caso, sono disponibili solo le ultime due opzioni e l'utilizzo di volatile è più efficiente.

+0

Oh, pensavo che lo zucchero volatile fosse più o meno sintattico per "dare a questa variabile un blocco e sincronizzarlo sempre quando si accede a questa variabile". Bello sapere che non è così. –

+0

Si noti che l'inizializzazione statica avviene in un blocco sincronizzato; è così che la visibilità (e altre proprietà) è garantita. – erickson

1

È necessario dichiarare che il lavoratore è volatile.

causa di due motivi

  1. se non dichiari come volatile rispetto a causa di riordino e di visibilità effetti si vedeva riferimento non nullo per costruire incompleta lavoratore oggetto. e quindi potresti avere effetti indesiderati. Questo non accadrebbe se rendi il tuo lavoratore immutabile usando tutte le variabili finali .

  2. In teoria potrebbe essere possibile che il thread principale non visualizzi il riferimento non nullo dell'oggetto di lavoro per un lungo periodo di tempo. Quindi evitalo.

Quindi, concludendo se il lavoratore è immutabile rispetto anche al punto 2, dovresti renderlo volatile. Evita sempre risultati imprevedibili. La dichiarazione di volatilità si prenderà cura di questi problemi.

0

Avrei dovuto usare ava.util.concurrent.atomic.AtomicReference se è nullo, iether lancia o aspetta e riprova (se init è abbastanza veloce).

1

Uso bloccaggio semplice per la richiesta iniziale:

public synchronized void init() { 
    if(worker!=null) return; 
    runInNewThread({ 
     synchronized(Server.this){ 
      worker = new WorkingThing(); 
      Server.this.notify(); 
     } 
    }); 
    this.wait(); 
} 

public Response handleRequest(Request req) { 
    if (worker == null) synchronized(this) { 
     this.wait(); 
    } 
    return worker.work(req); 
} 

Questo funziona perché ci sono punti di sincronizzazione tra accessi al lavoratore.

+1

Non aspettare mai fuori da un loop. Se ti manca il segnale di notifica potresti aspettare per sempre. E potrebbe esserci più di un thread in attesa => usa notifyAll. E non ha senso aspettare alla fine di init. – assylias

+0

Argh, a destra - dovrebbe essere il controllo a doppio blocco nel handleRequest synchronized (this) block. –