2016-05-06 14 views
5

Il blocco sincronizzato su System.out.println(number); richiede il seguente codice?È necessario questo blocco sincronizzato?

import java.util.concurrent.CountDownLatch; 

public class Main { 

    private static final Object LOCK = new Object(); 

    private static long number = 0L; 

    public static void main(String[] args) throws InterruptedException { 

     CountDownLatch doneSignal = new CountDownLatch(10); 

     for (int i = 0; i < 10; i++) { 
      Worker worker = new Worker(doneSignal); 
      worker.start(); 
     } 

     doneSignal.await(); 

     synchronized (LOCK) { // Is this synchronized block need? 
      System.out.println(number); 
     } 
    } 

    private static class Worker extends Thread { 

     private final CountDownLatch doneSignal; 

     private Worker(CountDownLatch doneSignal) { 
      this.doneSignal = doneSignal; 
     } 

     @Override 
     public void run() { 
      synchronized (LOCK) { 
       number += 1; 
      } 
      doneSignal.countDown(); 
     } 

    } 
} 

Penso che sia necessario perché c'è la possibilità di leggere il valore memorizzato nella cache.

Ma qualche persona dicono che:

E 'inutile.
Perché quando il thread principale legge la variabile number, tutto il thread di lavoro ha eseguito l'operazione di scrittura in memoria della variabile number.

risposta

5

doneSignal.await() è una chiamata di blocco, così il vostro main() procederà solo quando tutti i tuoi Worker le discussioni hanno chiamato doneSignal.countDown(), rendendolo raggiungere lo 0, che è ciò che rende il ritorno await() metodo.

Non è necessario aggiungere il blocco synchronized prima dello System.out.println(), tutti i thread sono già stati eseguiti in quel momento.

Considerare l'utilizzo di AtomicInteger per number anziché la sincronizzazione con un blocco per chiamare += 1.

+3

Non importa se i thread sono completati o meno, è importante che il lettore possa vedere il valore numerico attuale. Fortunatamente, coppia countdown() + await() garantisce prima :) :) – qwwdfsad

+0

tutto ciò che conta per 'doneSignal.await()' è che il 'CountDownLatch' raggiunge 0, ciò che succede a 'number' non ha alcuna importanza. Se ha rimosso del tutto il 'CountDownLatch', il thread principale potrebbe effettivamente essere fatto prima che tutti i suoi thread' Worker' vengano eseguiti, a seconda dello scheduler, e stampare sia '0' o qualche altro numero –

+2

Penso ** qwwdfsad ** ' Il punto è che se non ci fosse * prima-prima * tra il codice prima di ogni "countDown()" e il codice dopo "await()", si potrebbe ancora vedere un valore inferiore a 10 per "numero". Se per esempio 'await()' ha funzionato controllando un valore che è stato aggiornato con la vecchia semantica 'volatile' (pre-Java 5.0), poteva vedere in modo affidabile che tutti i thread hanno finito, ma non poteva vedere in modo affidabile l'ultimo valore di 'number'. –

1

Non è necessario:

CountDownLatch doneSignal = new CountDownLatch(10); 

for (int i = 0; i < 10; i++) { 
    Worker worker = new Worker(doneSignal); 
    worker.start(); 
} 
doneSignal.await(); 
// here the only thread running is the main thread 

Poco prima di morire ogni conto alla rovescia filo del countDownLatch

@Override 
public void run() { 
    synchronized (LOCK) { 
    number += 1; 
    } 
    doneSignal.countDown(); 
} 

Solo quando il filo 10 terminare il loro lavoro il doneSignal.await(); la linea sarà superata.

1

Non è necessario perché si sta aspettando il segnale "completato". Quella scarica memoria in modo che tutti i valori del thread atteso diventino visibili al thread principale.

Tuttavia è possibile verificare che facilmente, fare all'interno del metodo run un calcolo che richiede diversi (milioni) passi e non ottenere ottimizzato dal compilatore, se si vede un valore diverso dal valore finale che vi aspettate allora il tuo valore finale non era già visibile al thread principale. Ovviamente qui la parte critica è assicurarsi che il calcolo non venga ottimizzato, quindi è probabile che un semplice "incremento" venga ottimizzato. Questo in generale è utile per testare la concorrenza in cui non si è sicuri se si hanno le barriere di memoria corrette in modo che possano tornare utili in seguito.

-1

Needed

No.

Tuttavia, poiché non v'è alcuna (documentato) la garanzia che non ci sarà alcun interleaving è possibile trovare voci di registro intercalate.

System.out.println("ABC"); 
System.out.println("123"); 

potrebbe stampa:

AB1 
23C 

Worthwhile

Quasi certamente no. La maggior parte delle JVM implementa println con un blocco open JDK does.

caso Riva

Come suggerisce @DimitarDimitrov, v'è un ulteriore uso per detta serratura ed è di garantire una barriera memoria è attraversata befor accesso number. Se questa è la preoccupazione, allora non è necessario per lock, tutto ciò che devi fare è fare numbervolatile.

private static volatile long number = 0L; 
+0

La preoccupazione dell'OP è legata al vedere un valore correttamente aggiornato per 'number'. Questa preoccupazione non può essere risolta con le implementazioni di 'println()' sincronizzate' (a meno che gli aggiornamenti di 'number' non vengano eseguiti mentre si è nello stesso monitor, che molto probabilmente è' System.out'). –

+0

Informazioni sull'aggiornamento 'volatile' -' volatile' è completamente ridondante nell'esempio di OP se viene mantenuto 'doneSignal.await()'. Nella sua forma attuale, la tua risposta non sembra veramente corretta (o almeno non è chiaro come si applica alla domanda originale). –

1

synchronized non è necessaria intorno System.out.println(number);, ma non perché i PrintWriter.println() implementazioni sono sincronizzati internamente o perché quando doneSignal.await() sblocca tutti i thread di lavoro hanno finito.

synchronized non è necessaria perché c'è un accade-prima edge between tutto prima di ogni chiamata al doneSignal.countDown e il completamento della doneSignal.await(). Ciò garantisce che verrà visualizzato correttamente il valore corretto di number.

Problemi correlati