2013-06-07 15 views
10

Assumendo:Se si dispone di un solo thread di scrittura, è mai necessario un concorrente speciale?

  1. C'è solo un filo specifico che mai imposta un determinato campo di riferimento (non un lungo o doppia, quindi scrive ad esso sono atomico)
  2. Ci sono un certo numero di fili che potrebbero letto che stesso campo
  3. letture leggermente stantio sono accettabili (fino a pochi secondi)

In questo scenario, è necessario fare AtomicReference volatile o o qualcosa di simile?

This article stati:

barriere di memoria non sono necessari se aderire rigorosamente al principio solo scrittore.

Il che sembra suggerire che nel caso che sto descrivendo, davvero non è necessario fare nulla di speciale.

Quindi, ecco un test mi sono imbattuto con risultati curiosi:

import org.junit.Test; 

public class ThreadTest { 
    int onlyWrittenByMain = 0; 
    int onlyWrittenByThread = 0; 

    @Test 
    public void testThread() throws InterruptedException { 
     Thread newThread = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       do { 
        onlyWrittenByThread++; 
       } while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10); 
       System.out.println("thread done"); 
      } 
     }); 
     newThread.start(); 

     do { 
      onlyWrittenByMain++; 
      // Thread.yield(); 
      // System.out.println("test"); 
      // new Random().nextInt(); 
     } while (onlyWrittenByThread < 10); 
     System.out.println("main done"); 
    } 
} 

volte in esecuzione questa uscita volontà "filo fatto" e poi appendere per sempre. A volte è completo. Quindi il thread vede le modifiche apportate dal thread principale, ma apparentemente main non vede sempre la modifica apportata dal thread?

Se inserisco il sistema, o Thread.yield, o la chiamata casuale o rendiamo volatile soloWrittenByThread, esso viene completato ogni volta (provato circa 10 volte).

Ciò significa che il post di blog di cui faccio il riferimento sopra non è corretto? Che devi avere una barriera di memoria anche nello scenario single writer?

rapporto

Nessuno abbastanza risposto a questa domanda in modo penso di Immagino che è probabile corretto che una barriera di memoria non è necessaria, ma senza qualcosa per creare il caso, prima, il compilatore Java e hotspot può fare ottimizzazioni (ad esempio, sollevamento) che lo farà non fare ciò che si vuole.

+2

Hai ancora di preoccuparsi di una scrittura incompleta, vale a dire la lettura la variabile quando è stato solo in parte scritto. Se le scritture sono atomiche, stai bene. – theglauber

+3

È necessario almeno "volatile". Non ci sono garanzie che una scrittura su una variabile non volatile da un thread sarà _ever_ visibile da altri thread. 'volatile' lo garantisce! – fge

+1

@ fge, puoi fornire una documentazione definitiva per questo? Sto cercando di leggere il capitolo 17 delle specifiche, ma non vedo che cosa supporta la tua richiesta. Però, forse è la mancanza di una dichiarazione che confuta è il punto. – mentics

risposta

1

È necessario perché le modifiche sul campo di lettura potrebbero non essere visibili ai thread del lettore. È necessario creare un evento prima della relazione.

5

Il problema è il caching su un sistema multicore - senza qualcosa di volatile che forzasse la relazione di happen-before (materiale di barriera di memoria) si potrebbe avere il thread del writer scrivendo su una copia della variabile nel suo core e tutto il resto thread del lettore che leggono un'altra copia della variabile su un altro core. L'altro problema è l'atomicità, a cui risponde un'altra risposta.

+0

La situazione che descrivi riguarda la stoltezza dei dati, non è vero? Se il thread del lettore legge i dati una volta al secondo, non è garantito per vedere finalmente il valore che il thread del writer ha scritto ad un certo punto? – mentics

+0

Dipende dal modello di consistenza della memoria della macchina sottostante ... non è probabile che sia un problema. Il punto è che il JLS non specifica che deve farlo - è la ragione per la relazione accade-prima nel JMM. – selig

2

Il problema principale nel codice non è tanto quello che farà la CPU, ma ciò che la JVM farà con esso: si ha un alto rischio di sollevamento variabile. Ciò significa che il JMM (Java Memory Model) consente una JVM di riscrivere:

public void run() { 
    do { 
     onlyWrittenByThread++; 
    } while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10); 
    System.out.println("thread done"); 
} 

come questo altro pezzo di codice (notare le variabili locali):

public void run() { 
    int localA = onlyWrittenByMain; 
    int localB = onlyWrittenByThread; 
    do { 
     localB ++; 
    } while (localA < 10 || localB < 10); 
    System.out.println("thread done"); 
} 

Succede che questo è una ottimizzazione abbastanza comune fatta da hotpost.Nel tuo caso, una volta effettuata l'ottimizzazione (probabilmente non subito quando chiami quel metodo ma dopo alcuni millisecondi), qualunque cosa tu faccia in altri thread sarà mai visibile da quel thread.

+0

Giusto, questo è quello che stavo indovinando anche io. La parola chiave volatile precluderebbe quell'ottimizzazione. – mentics

+0

Per evitare il sollevamento, è necessaria una qualche forma di sincronizzazione. In tal caso, le variabili volatili sarebbero probabilmente il modo più economico. – assylias

1

Immagino che tu abbia frainteso le parole che hai citato dal blog di Martin. Su un hardware x86/64 è possibile utilizzare Atomic * .lazySet per ottenere prestazioni molto più elevate di "volatili" poiché fornisce una barriera per i punti vendita che è molto più economica della barriera di carico del negozio. Per quanto ho capito, dovresti usare AtomicLong invece e usare lazySet per garantire che i codici non vengano riordinati.

Si prega di fare riferimento a questo articolo per ulteriori dettagli: http://psy-lob-saw.blogspot.com/2012/12/atomiclazyset-is-performance-win-for.html

Problemi correlati