2016-01-07 22 views
5

Nel seguente codice, se uso l'istruzione sysout all'interno di loop, il codice viene eseguito e passa all'interno del ciclo dopo la condizione soddisfatta, ma se non uso l'istruzione sysout all'interno loop poi poi il ciclo infinito continua senza entrare nella condizione if anche se la condizione if è soddisfatta .. qualcuno può aiutarmi per favore a trovare il motivo esatto per questo. Solo una dichiarazione sysout fa sì che la condizione if diventi vera. perché è così?Ritardo nel thread in esecuzione a causa di system.out.println statement

Il codice è il seguente: -

class RunnableDemo implements Runnable { 
    private Thread t; 
    private String threadName; 

    RunnableDemo(String name){ 
     threadName = name; 
     System.out.println("Creating " + threadName); 
    } 
    public void run() { 
     System.out.println("Running " + threadName); 
     for(;;) 
     { 
      //Output 1: without this sysout statement. 
      //Output 2: After uncommenting this sysout statement 
      //System.out.println(Thread.currentThread().isInterrupted()); 
      if(TestThread.i>3) 
      { 
       try { 
        for(int j = 4; j > 0; j--) { 
         System.out.println("Thread: " + threadName + ", " + j); 
        } 
       } catch (Exception e) { 
        System.out.println("Thread " + threadName + " interrupted."); 
       } 
       System.out.println("Thread " + threadName + " exiting."); 
      } 
     } 
    } 

    public void start() 
    { 
     System.out.println("Starting " + threadName); 
     if (t == null) 
     { 
     t = new Thread (this, threadName); 
     t.start(); 
     } 

    } 

} 

public class TestThread { 
     static int i=0; 
    public static void main(String args[]) { 

     RunnableDemo R1 = new RunnableDemo("Thread-1"); 
     R1.start(); 
     try { 
     Thread.sleep(10000); 
    } catch (InterruptedException e) { 

     e.printStackTrace(); 
    } 

    i+=4; 
    System.out.println(i); 
    } 
} 

uscita senza sysout economico nel ciclo infinito: - enter image description here

uscita con la dichiarazione sysout nel ciclo infinito: - enter image description here

+2

Im che va indovinare si esegue in problemi volatili. Prova a creare static int i = 0; static volatile int i = 0; Se il comportamento è ora lo stesso mal si mette una risposta con una spiegazione. –

+0

@Aaron È un loop infinito, quindi continuano a eseguirlo costantemente, giusto ...? EDIT: Aaron ha lasciato l'edificio. –

+1

@Henk de Boer, ho provato a utilizzare volatile e funziona bene. Ma puoi spiegare che cosa cambia l'istruzione sysout che permette al codice di funzionare anche senza usare parole chiave volatili. –

risposta

3

Il problema qui può essere risolto modificando

static int i=0;

a

static volatile int i=0;

Fare una variabile volatile ha una serie di conseguenze complesse e io non sono un esperto in questo. Quindi, proverò a spiegare come ci penso.

La variabile i è presente nella memoria principale, la RAM. Ma la RAM è lenta, quindi il tuo processore la copia nella memoria più veloce (e più piccola): la cache. Cache multiple in effetti, ma questo è irrilevante.

Ma quando due thread su due processori diversi mettono i loro valori in cache diverse, cosa succede quando il valore cambia? Bene, se il thread 1 modifica il valore nella cache 1, il thread 2 utilizza ancora il vecchio valore dalla cache 2. A meno che non diamo a entrambi i thread che questa variabile i potrebbe cambiare in qualsiasi momento come se fosse magia. Questo è ciò che fa la parola chiave volatile.

Quindi perché funziona con l'istruzione di stampa? Bene, la dichiarazione di stampa richiama molto codice dietro le quinte. Alcuni di questi codici contengono probabilmente un blocco sincronizzato o un'altra variabile volatile, che (per errore) aggiorna anche il valore di i in entrambe le cache. (Grazie a Marco13 per averlo indicato).

La prossima volta che si tenta di accedere a i, si ottiene il valore aggiornato!

PS: sto dicendo RAM qui, ma è probabilmente la memoria condivisa più vicina tra i due thread, che potrebbe essere una cache se sono hyperthreaded per esempio.

Questa è una grande spiegazione troppo (con le immagini!):

http://tutorials.jenkov.com/java-concurrency/volatile.html

+0

Un'altra cosa che voglio chiedere, se uso thread.sleep invece di sysout, anche allora la condizione if diventa vera. Thread.sleep fa anche un po 'di memoria e svuota i dati dalla cache? –

+0

@ Marco13 Ok, adatterò la risposta. Grazie per la correzione! –

+0

@MeenakshiKumari Il modo migliore per scoprirlo è provarlo tu stesso! :) Ma no, non lo penserei, a meno che (ancora una volta per caso) qualche pezzo di codice estraneo attivi una cache chiara mentre il sonno è in esecuzione. Come ho detto, non sono un esperto e il threading è spesso molto più complicato nella pratica di quanto sembri. Quindi sono riluttante a darti regole molto concrete qui, mi dispiace. Il sonno stesso sta solo dicendo al sistema operativo che non hai nulla da fare per ora, nient'altro, quindi non dovresti fare affidamento su di esso con proprietà speciali come lo svuotamento della memoria. –

2

Quando si accede a un valore variabile, il cha nges non vengono scritti (o caricati da) la posizione di memoria effettiva ogni volta. Il valore può essere caricato in un registro CPU, o memorizzato nella cache, e rimanere lì fino a quando le cache vengono scaricate. Inoltre, poiché TestThread.i non viene affatto modificato all'interno del ciclo, l'ottimizzatore potrebbe decidere di sostituirlo con un controllo prima del ciclo e rimuovere completamente la dichiarazione if (non penso che stia effettivamente accadendo nel tuo caso, ma il punto è che è ).

L'istruzione che rende il filo per irrigare le sue cache e sincronizzarli con il contenuto corrente della memoria fisica è chiamato memoria barriera. Esistono due modi in Java per forzare una barriera di memoria: immettere o uscire da un blocco synchronized o accedere a una variabile volatile. Quando si verifica uno di questi eventi, la cache viene svuotata e al thread è garantito sia di vedere una vista aggiornata del contenuto della memoria, sia di avere tutte le modifiche apportate localmente alla memoria.

Così, come suggerito nei commenti, se il vostro dichiarare TestThread.i come volatile, il problema andrà via, perché ogni volta che il valore viene modificato, il cambiamento sarà impegnato immediatamente, e l'ottimizzatore sapere di non ottimizzatore, e che il controllo lontano dal ciclo e non memorizzare nella cache il valore.

Ora, perché aggiungere un'istruzione di stampa modifica il comportamento? Bene, c'è molta sincronizzazione in corso all'interno di io, il thread colpisce una barriera di memoria da qualche parte, e carica il valore fresco. Questa è solo una coincidenza.

+0

Non sto dicendo che ti sbagli, ma l'ottimizzatore è autorizzato a formulare ipotesi così pesanti sulle variabili dei membri? Sembra soggetto a errori. –

+0

Certo, perché no? Se non vi è alcuna barriera di memoria all'interno del ciclo e una variabile non viene modificata dal thread corrente, è del tutto sicuro assumere che rimanga costante. In effetti, rendere questa assunzione è più sicuro che non farlo, perché rende il comportamento deterministico. – Dima

+0

Sì, ma come fa a sapere in fase di compilazione che il valore di i non cambierà prima della generazione del thread? forse mi manca qualcosa. –