2012-07-08 5 views
9

Sapendo cheUsi di volatili senza sincronizzazione

letture e scritture sono atomic per tutte le variabili dichiarate volatili

Question1: Può questo essere inteso come se

private volatile int x = 0; 

x++; l'operazione è atomica?

E che

Marcatura variabile volatile, non elimina tutti hanno bisogno di sincronizzare azioni atomiche, perché memory consistency errors are still possible.

Question2: Mi chiedo in quali circostanze (se presente) è possibile per vedere una variabile contrassegnata con volatile e non vedere alcun metodo di blocchi contrassegnati sincronizzati (che tenta di accedere/modificare la variabile)?

In altre parole, tutte le variabili che devono essere protette da modifiche concorrenti devono essere contrassegnate con volatile?

risposta

11

Il volatile offre solo garanzie di visibilità aggiuntive, scritture/letture atomiche per long/double (altrimenti non garantito da JLS, sì) e alcune garanzie di ordine di memoria. No sincronizzazione (è possibile però creare blocchi di sincronizzazione a partire da solo volatile - Dekker's algorithm) Quindi no, non ti aiuta con x++ - che è ancora una lettura, inc e scrittura e necessita di qualche forma di sincronizzazione.

Un esempio di volatile è il famoso blocco ricontrollato, dove si evita la sincronizzazione la maggior parte del tempo perché le garanzie di ordinazione sono tutti abbiamo bisogno:

private volatile Helper helper = null; 
public Helper getHelper() { 
    if (helper == null) { 
     synchronized(this) { 
      if (helper == null) { 
       helper = new Helper(); 
      } 
     } 
    } 
    return helper; 
} 

Un esempio in cui non c'è assolutamente alcuna sincronizzazione coinvolti, è una semplice uscita di bandiera, qui non si tratta di garanzie che ordinano ma solo la visibilità garantita

public volatile boolean exit = false; 
public void run() { 
    while (!exit) doStuff(); 
    // exit when exit set to true 
} 

Se un altro thread imposta exit = true l'altro thread facendo il ciclo while è garantito per vedere t aggiorna - senza volerlo non può.

+0

risposta eccellente e con l'esempio. Grazie. – JAM

6

x ++; l'operazione è atomica?

No. Si riduce a x = x + 1. La lettura di x è atomica e la scrittura su x è atomica, ma l'intero x = x + 1 non è atomico.

Chissà in quali casi (se presente) è possibile vedere una variabile contrassegnato volatile e non vedi metodi di blocchi contrassegnati sincronizzati (che tentare di accedere/modificare la variabile)?

Bene, ci sono tutti i tipi di approcci alla concorrenza che non usano synchronized. C'è una vasta gamma di altre utilità di blocco in Java e algoritmi lock-free che richiedono ancora cose come volatile: ConcurrentLinkedQueue è un esempio specifico, anche se fa ampio uso di atomici "magici" compareAndSet.

0

Come esempio rapidamente verificabile che possono illustrare le risposte precedenti, questo produce sempre un conteggio finale di 8:

import java.util.concurrent.atomic.AtomicInteger; 


public class ThreadTest_synchronize { 

public static void main(String[] args) { 

    ThreadTest_synchronize tt = new ThreadTest_synchronize(); 
    try { 
     tt.go(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 

} 

private void go() throws InterruptedException{ 

    MyRunnable t = new MyRunnable(); 
    Thread myThread_1 = new Thread(t, "t1"); 
    Thread myThread_2 = new Thread(t, "t2"); 
    myThread_1.start(); 
    myThread_2.start(); 
    myThread_1.join(); 
    myThread_2.join(); 
    System.out.println("Processing count="+t.getCount());  

} 

private class MyRunnable implements Runnable{ 

    private AtomicInteger count=new AtomicInteger(0); 

    @Override 
    public void run() { 
     for(int i=1; i< 5; i++){ 
      doSomething(i); 
      count.getAndAdd(1); 
     }   
    } 


    public AtomicInteger getCount() { 
     return this.count; 
    } 


    private void doSomething(int i) { 
     try { 
      Thread.sleep(i*300); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
}  

} 

mentre questo non lo fa in genere:

public class ThreadTest_volatile { 

public static void main(String[] args) { 

    ThreadTest_volatile tt = new ThreadTest_volatile(); 
    try { 
     tt.go(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 

} 

private void go() throws InterruptedException{ 

    MyRunnable t = new MyRunnable(); 
    Thread myThread_1 = new Thread(t, "t1"); 
    Thread myThread_2 = new Thread(t, "t2"); 
    myThread_1.start(); 
    myThread_2.start(); 
    myThread_1.join(); 
    myThread_2.join(); 
    System.out.println("Processing count="+t.getCount());  

} 

private class MyRunnable implements Runnable{ 

    private volatile int count = 0; 


    @Override 
    public void run() { 
     for(int i=1; i< 5; i++){ 
      doSomething(i); 
      count++; 
     } 

    } 

    private int add(int count){ 
     return ++count; 
    } 


    public int getCount(){ 
     return count; 
    } 

    private void doSomething(int i) { 

     try { 
      Thread.sleep(i*300); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 


}