2010-04-05 9 views
8

Il modello java meomry richiede che i blocchi synchronize sincronizzati sullo stesso monitor applichino un rendering prima-dopo sulle variabili modificate all'interno di tali blocchi. Esempio:Modello di memoria Java: riordinamento e blocchi simultanei

// in thread A 
synchronized(lock) 
{ 
    x = true; 
} 

// in thread B 
synchronized(lock) 
{ 
    System.out.println(x); 
} 

In questo caso è garantito che filo B vedrà x==true finché filo A già passato che synchronized -block. Ora sono in procinto di riscrivere un sacco di codice per utilizzare i blocchi più flessibili (e detti per essere più veloci) in java.util.concurrent, in particolare lo ReentrantReadWriteLock. Così, l'esempio si presenta così:

EDIT: L'esempio è stato rotto, perché ho erroneamente trasformato il codice, come notato da matt b. Fissati come segue:

// in thread A 
lock.writeLock().lock(); 
{ 
    x = true; 
} 
lock.writeLock().unlock(); 

// in thread B 
lock.readLock().lock(); 
{ 
    System.out.println(x); 
} 
lock.readLock().unlock(); 

Tuttavia, non ho visto alcun suggerimento all'interno della specificazione modello di memoria che tali serrature anche implicano l'ordinamento nessessary. Analizzando l'implementazione sembra basarsi sull'accesso a variabili volatili all'interno di AbstractQueuedSynchronizer (almeno per l'implementazione del sole). Tuttavia questo non fa parte di alcuna specifica e inoltre l'accesso alle variabili non volatili non è realmente contemplato dalla barriera di memoria data da queste variabili, vero?

Così, qui sono le mie domande:

  • E 'lecito ritenere lo stesso ordinamento come con i "vecchi" synchronized blocchi?
  • È documentato da qualche parte?
  • L'accesso a qualsiasi variabile volatile è una barriera di memoria per qualsiasi altra variabile?

Saluti, Steffen

-

Commento a Yanamon:

un'occhiata al codice seguente:

// in thread a 
x = 1; 
synchronized (a) { y = 2; } 
z = 3; 

// in thread b 
System.out.println(x); 
synchronized (a) { System.out.println(y); } 
System.out.println(z); 

Da quello che ho capito, la barriera di memoria fa rispettare il secondo output mostra 2, ma non ha alcun effetto garantito sulle altre variabili ...? Quindi, come può essere paragonato all'accesso a una variabile volatile?

+0

Una nota sul codice che hai aggiunto, il thread b stamperà solo 2 se ottiene il blocco per un thread precedente a ... che è stato un po 'implicito ma volevo solo chiarirlo. Ma per rispondere a questa domanda volatile, si può usare volatile nel modo seguente per rafforzare la visibilità: -------- volatile memoria booleana Barrier = falso; int unguardedValue = 0; // thread a: unguardedValue = 10; memoryBarrier = true; // thread b if (memoriaBarrier) { // unguardedValue è garantito per essere letto come 10; } – Yanamon

+0

Beh, immagino che scrivere codice nei commenti non funzioni davvero bene, ho aggiornato la mia risposta con un esempio – Yanamon

risposta

5

Dal API-doc:

implementazioni

Tutto blocco devono far rispettare lo stesso di sincronizzazione di memoria semantica come previsto dalla funzione built-in blocco del monitor, come descritto nella Java Language Specification, terza edizione (17,4 Memory Model):

* A successful lock operation has the same memory synchronization effects as a successful Lock action. 
* A successful unlock operation has the same memory synchronization effects as a successful Unlock action. 

blocco e lo sblocco non riuscita operazioni, e reentrant operazioni di blocco/sblocco, non richiedere effetti di sincronizzazione della memoria .

+0

Hai assolutamente ragione. Ho letto un sacco di cose, ma mi sembra di aver perso completamente l'interfaccia Lock stessa ... –

+0

Puoi commentare la domanda volatile? –

+0

@Steffen Heil: se lo richiamo correttamente, qualsiasi accesso a una variabile volatile ha solo effetto di sincronizzazione con altri accessi alla stessa variabile, cioè non è garantito che fornisca una sorta di barriera di memoria generale. Una rapida scansione del JLS sembra confermare questo ricordo. Ma prendilo con un granello di sale, visto che è passato un po 'di tempo dall'ultima volta che ho avuto un incontro ravvicinato con il modello di memoria e le sue implicazioni ... – Dirk

4

Oltre alla questione di ciò che garantisce la semantica del modello di memoria, penso che ci siano alcuni problemi con il codice che stai postando.

  1. si sincronizza due volte sullo stesso blocco - questo non è necessario. Quando si utilizza un'implementazione Lock, non è necessario utilizzare il blocco synchronized.
  2. L'idioma standard per l'utilizzo di Lock è quello di eseguire un blocco try-finally per impedire lo sblocco accidentale del blocco (poiché il blocco non viene rilasciato automaticamente quando si immette qualsiasi blocco in cui ci si trova, come nel blocco synchronized).

Si deve usare un Lock con qualcosa di simile:

lock.lock(); 
try { 
    //do stuff 
} 
finally { 
    lock.unlock(); 
} 
+0

Hai ragione, il mio esempio era rotto. Ho risolto la domanda. E sì, di solito sto usando try/finally, lasciandolo qui per mancanza di tempo. –

1

Lettura e scrittura di variabili volatili ora impone che accade prima e dopo l'operazione avviene l'ordinazione. La scrittura su una variabile volatile ha lo stesso effetto del rilascio di un monitor e la lettura di una variabile ha l'effetto di acquisire un monitor. L'esempio seguente rende un po 'più chiaro:

volatile boolean memoryBarrier = false; 
int unguardedValue = 0; 

//thread a: 
unguardedValue = 10; 
memoryBarrier = true; 

// thread b 
if (memoryBarrier) { 
    // unguardedValue is guaranteed to be read as 10; 
} 

Ma tutti detto il codice di esempio che hai fornito non sembrava come se fosse stato veramente usando il ReentrantLock come è stato progettato per essere utilizzato.

  1. circonda l'uso di un Lock con costruito nel syncronized parola chiave che rende efficacemente l'accesso del del Java per il blocco già a thread singolo in modo da non dare al Lock la possibilità di fare una vera e propria opera.
  2. L'acquisizione di un rilascio di una Lock dovrebbe essere fatto seguendo il modello di seguito, questo è descritto nella documentazione java di Lock

lock.readLock().lock(); 
try { 
    // Do work 
} finally { 
    lock.readLock.unlock(); 
} 

+0

Vedere il commento in questione, per favore. –

+0

Capisco che il thread b non vedrà unguardedValue perché l'ordine non influisce sulla visibilità e unguardedValue non è volatile, quindi, non necessariamente visibile dal thread b. È giusto ? –

1

Yanamon, io non sono sicuro lei ha ragione - ma per ragioni diverse dall'argomento che stai facendo.

La variabile non sorvegliata può essere riordinata nel thread "a" in modo che il suo valore sia impostato su 10 dopo memoryBarrier è impostato su true.

"Non v'è alcuna garanzia che le operazioni in un thread verranno eseguite nell'ordine in programma, purché il riordino non è rilevabile all'interno che filo - anche se il riordino è evidente ad altri thread "

Java Concurrency in Practice, Brian Goetz, p34

aggiornamento: quello che ho detto è vero nel caso del vecchio modello di memoria. Quindi, se vuoi scrivere una volta sola, ovunque, la mia argomentazione è valida. Tuttavia, nel nuovo modello di memoria, non è il caso in quanto la semantica che circonda il riordino di variabili non volatili in presenza di un accesso volatile è diventata più severa (vedere http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile).

Problemi correlati