Domanda

Oggi al lavoro mi sono imbattuto nella parola chiave volatile in Java. Non conoscendone molto, ho trovato questa spiegazione:

  

Teoria e pratica di Java: gestione della volatilità

Dati i dettagli in cui quell'articolo spiega la parola chiave in questione, la usi mai o potresti mai vedere un caso in cui potresti usare questa parola chiave nel modo corretto?

È stato utile?

Soluzione

volatile ha una semantica per la visibilità della memoria. Fondamentalmente, il valore di un campo volatile diventa visibile a tutti i lettori (in particolare altri thread) dopo che un'operazione di scrittura è stata completata su di esso. Senza volatile , i lettori potrebbero vedere un valore non aggiornato.

Per rispondere alla tua domanda: Sì, utilizzo una variabile volatile per controllare se un po 'di codice continua un ciclo. Il ciclo verifica il valore volatile e continua se è true . La condizione può essere impostata su false chiamando a " stop " metodo. Il ciclo vede false e termina quando verifica il valore dopo che il metodo stop ha completato l'esecuzione.

Il libro " Java Concurrency in Practice , " che consiglio vivamente, fornisce una buona spiegazione di volatile . Questo libro è stato scritto dalla stessa persona che ha scritto l'articolo IBM a cui fa riferimento la domanda (in effetti, cita il suo libro in fondo a quell'articolo). Il mio uso di volatile è ciò che il suo articolo chiama "flag di stato modello 1". & Quot;

Se vuoi saperne di più su come volatile funziona sotto il cofano, leggi su il modello di memoria Java . Se vuoi andare oltre quel livello, dai un'occhiata a un buon libro sull'architettura del computer come Hennessy & amp; Patterson e leggi sulla coerenza della cache e sulla coerenza della cache.

Altri suggerimenti

& # 8220; & # 8230; il modificatore volatile garantisce che qualsiasi thread che legge un campo visualizzi l'ultimo valore scritto. & # 8221; - Josh Bloch

Se stai pensando di utilizzare volatile , leggi sul pacchetto java.util.concurrent che si occupa del comportamento atomico.

Il post di Wikipedia su un Singleton Pattern mostra un uso volatile.

Punto importante su volatile :

  1. La sincronizzazione in Java è possibile utilizzando le parole chiave Java sincronizzate e volatile e blocchi.
  2. In Java, non possiamo avere la variabile sincronizzata . L'uso della parola chiave sincronizzata con una variabile è illegale e comporterà un errore di compilazione. Invece di usare la variabile sincronizzata in Java, puoi usare la variabile java volatile , che istruirà i thread JVM a leggere il valore della variabile volatile dalla memoria principale e non memorizzarlo nella cache locale.
  3. Se una variabile non è condivisa tra più thread, non è necessario utilizzare la parola chiave volatile .

source

Esempio di utilizzo di volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

Stiamo creando pigramente l'istanza nel momento in cui arriva la prima richiesta.

Se non rendiamo la variabile _instance volatile , il thread che sta creando l'istanza di Singleton non è in grado di comunicare con altro thread. Quindi se il thread A sta creando l'istanza Singleton e subito dopo la creazione, la CPU corrompe ecc., Tutti gli altri thread non saranno in grado di vedere il valore di _instance come non null e crederanno che sia ancora assegnato null .

Perché succede? Poiché i thread del lettore non eseguono alcun blocco e fino a quando il thread del writer non esce da un blocco sincronizzato, la memoria non verrà sincronizzata e il valore di _instance non verrà aggiornato nella memoria principale. Con la parola chiave Volatile in Java, questo è gestito da Java stesso e tali aggiornamenti saranno visibili da tutti i thread del lettore.

  

Conclusione : la parola chiave volatile viene utilizzata anche per comunicare il contenuto della memoria tra i thread.

Esempio di utilizzo di senza volatile:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

Il codice sopra non è thread-safe. Sebbene controlli ancora una volta il valore dell'istanza all'interno del blocco sincronizzato (per motivi di prestazioni), il compilatore JIT può riorganizzare il bytecode in modo che il riferimento all'istanza sia impostato prima che il costruttore abbia terminato la sua esecuzione. Ciò significa che il metodo getInstance () restituisce un oggetto che potrebbe non essere stato inizializzato completamente. Per rendere il codice thread-safe, la parola chiave volatile può essere utilizzata da Java 5 per la variabile di istanza. Le variabili contrassegnate come volatili diventano visibili ad altri thread solo quando il costruttore dell'oggetto ha terminato completamente l'esecuzione.
Fonte

 inserisci qui la descrizione dell'immagine

volatile utilizzo in Java :

Gli iteratori fail-fast sono in genere implementati usando un contatore volatile sull'oggetto elenco.

  • Quando l'elenco viene aggiornato, il contatore viene incrementato.
  • Quando viene creato un Iterator , il valore corrente del contatore viene incorporato nell'oggetto Iterator .
  • Quando viene eseguita un'operazione Iterator , il metodo confronta i due valori del contatore e genera un ConcurrentModificationException se sono diversi.

L'implementazione di iteratori fail-safe è generalmente leggera. In genere si basano sulle proprietà delle strutture di dati dell'implementazione dell'elenco specifico. Non esiste un modello generale.

volatile è molto utile per interrompere i thread.

Non che dovresti scrivere i tuoi thread, Java 1.6 ha molti pool di thread carini. Ma se sei sicuro di aver bisogno di un thread, dovrai sapere come fermarlo.

Il modello che uso per i thread è:

public class Foo extends Thread {
  private volatile boolean close = false;
  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

Nota come non sia necessaria la sincronizzazione

Un esempio comune per l'utilizzo di volatile è l'uso di una variabile volatile booleana come flag per terminare un thread. Se hai avviato un thread e desideri essere in grado di interromperlo in modo sicuro da un thread diverso, puoi fare in modo che il thread controlli periodicamente un flag. Per fermarlo, imposta la bandiera su true. Rendendo il flag volatile , puoi assicurarti che il thread che lo controlla vedrà che è stato impostato la prossima volta che lo controlla senza nemmeno usare un blocco sincronizzato .

Una variabile dichiarata con la parola chiave volatile ha due qualità principali che la rendono speciale.

  1. Se abbiamo una variabile volatile, non può essere memorizzata nella cache della memoria del computer (microprocessore) da nessun thread. L'accesso è sempre avvenuto dalla memoria principale.

  2. Se è presente una operazione di scrittura su una variabile volatile e improvvisamente operazione di lettura è richiesto, è garantito che l'operazione di scrittura sia terminata prima dell'operazione di lettura .

Due qualità di cui sopra ne deducono

  • Tutti i thread che leggono una variabile volatile leggeranno sicuramente l'ultimo valore. Perché nessun valore memorizzato nella cache può inquinarlo. Inoltre, la richiesta di lettura verrà concessa solo dopo il completamento dell'operazione di scrittura corrente.

E d'altra parte,

  • Se esaminiamo ulteriormente il # 2 che ho citato, possiamo vedere che la parola chiave volatile è un modo ideale per mantenere una variabile condivisa che ha < em> 'n' numero di thread di lettura e solo un thread di scrittura per accedervi. Una volta aggiunta la parola chiave volatile , viene eseguita. Nessun altro overhead sulla sicurezza dei thread.

Al contrario,

Noi possiamo utilizziamo esclusivamente la parola chiave volatile per soddisfare una variabile condivisa che ha più di uno scrive thread accedendo ad esso .

Sì, volatile deve essere utilizzato ogni volta che si desidera accedere a una variabile mutabile da più thread. Non è un caso d'uso molto comune perché in genere è necessario eseguire più di una singola operazione atomica (ad esempio, controllare lo stato della variabile prima di modificarlo), nel qual caso si utilizzerà invece un blocco sincronizzato.

Nessuno ha menzionato il trattamento dell'operazione di lettura e scrittura per il tipo di variabile lunga e doppia. Lettura e scrittura sono operazioni atomiche per le variabili di riferimento e per la maggior parte delle variabili primitive, ad eccezione dei tipi di variabili lunghe e doppie, che devono utilizzare la parola chiave volatile per essere operazioni atomiche. @link

A mio avviso, due scenari importanti diversi dall'arresto del thread in cui viene utilizzata la parola chiave volatile sono:

  1. Meccanismo di blocco con doppio controllo . Utilizzato spesso nel design Singleton modello. In questo l'oggetto singleton deve essere dichiarato volatile .
  2. Wakeups spuri . A volte il thread può svegliarsi dalla chiamata in attesa anche se non è stata emessa alcuna chiamata di notifica. Questo comportamento è chiamato veglia suprema. Questo può essere contrastato usando una variabile condizionale (flag booleano). Metti la chiamata wait () in un ciclo while finché il flag è vero. Quindi, se il thread si riattiva dalla chiamata di attesa a causa di motivi diversi dalla notifica / notifica, allora incontra il flag è ancora vero e quindi le chiamate attendono di nuovo. Prima di chiamare la notifica impostare questo flag su true. In questo caso la bandiera booleana viene dichiarata volatile .

Dovrai utilizzare la parola chiave "volatile", o "sincronizzata" e qualsiasi altro strumento e tecnica di controllo della concorrenza che potresti avere a disposizione se stai sviluppando un'applicazione multithread. Esempio di tale applicazione sono le app desktop.

Se si sta sviluppando un'applicazione che verrebbe distribuita al server delle applicazioni (Tomcat, JBoss AS, Glassfish, ecc.) non è necessario gestire autonomamente il controllo della concorrenza poiché è già indirizzato dal server delle applicazioni. In effetti, se ricordassi correttamente lo standard Java EE proibire qualsiasi controllo di concorrenza in servlet ed EJB, poiché fa parte del livello di "infrastruttura" che si suppone essere libero di gestirlo. Esegui il controllo della concorrenza in tale app solo se stai implementando oggetti singleton. Questo è già stato risolto se hai lavorato a maglia i tuoi componenti usando frameworkd come Spring.

Quindi, nella maggior parte dei casi di sviluppo Java in cui l'applicazione è un'applicazione Web e utilizza un framework IoC come Spring o EJB, non è necessario utilizzare 'volatile'.

volatile garantisce solo che tutti i thread, anche se stessi, stanno incrementando. Ad esempio: un contatore vede contemporaneamente la stessa faccia della variabile. Non viene utilizzato al posto di sincronizzato o atomico o altre cose, rende completamente sincronizzate le letture. Si prega di non confrontarlo con altre parole chiave java. Come mostrato nell'esempio di seguito, anche le operazioni con variabili volatili sono atomiche e falliscono o hanno successo in una sola volta.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

Anche se metti risultati volatili o no differiranno sempre. Ma se usi AtomicInteger come sotto i risultati saranno sempre gli stessi. Lo stesso vale anche per la sincronizzazione.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

Sì, lo uso parecchio - può essere molto utile per il codice multi-thread. L'articolo che hai indicato è buono. Sebbene ci siano due cose importanti da tenere a mente:

  1. Dovresti usare volatile solo se lo fai capisci completamente cosa fa e in che modo differisce da sincronizzato. In molte situazioni appare volatile, in superficie, per essere più semplice alternativa performante a sincronizzato, quando spesso è meglio la comprensione di volatile renderebbe chiaro che sincronizzato è l'unico opzione che funzionerebbe.
  2. volatile in realtà non funziona in a molte JVM più vecchie, sebbene sincronizzato fa. Ricordo di aver visto un documento che faceva riferimento ai vari livelli di supporto in diverse JVM ma sfortunatamente non riesco a trovarlo ora. Sicuramente esaminalo se stai usando Java pre 1.5 o se non hai il controllo sulle JVM su cui verrà eseguito il tuo programma.

Assolutamente sì. (E non solo in Java, ma anche in C #.) Ci sono volte in cui è necessario ottenere o impostare un valore che è garantito essere un'operazione atomica sulla propria piattaforma, ad esempio un int o un valore booleano, ma non è necessario il sovraccarico del blocco del filo. La parola chiave volatile ti consente di assicurarti che quando leggi il valore ottieni il valore corrente e non un valore memorizzato nella cache che è stato appena reso obsoleto da una scrittura su un altro thread.

Ogni thread che accede a un campo volatile leggerà il suo valore corrente prima di continuare, anziché (potenzialmente) usare un valore memorizzato nella cache.

Solo la variabile membro può essere volatile o transitoria.

Esistono due diversi usi della parola chiave volatile.

  1. Impedisce a JVM di leggere i valori dal registro (si assume come cache) e impone che il suo valore venga letto dalla memoria.
  2. Riduce il rischio di errori di coerenza della memoria.
  

Impedisce a JVM di leggere i valori nel registro e forza i suoi   valore da leggere dalla memoria.

Un flag occupato viene utilizzato per impedire che un thread continui mentre il dispositivo è occupato e il flag non è protetto da un blocco:

while (busy) {
    /* do something else */
}

Il thread di test continuerà quando un altro thread disattiva il flag di occupato :

busy = 0;

Tuttavia, poiché si accede frequentemente a occupato nel thread di test, la JVM può ottimizzare il test inserendo il valore di occupato in un registro, quindi testare il contenuto del registro senza leggere il valore di occupato in memoria prima di ogni test. Il thread di test non vedrebbe mai il cambiamento di occupato e l'altro thread cambierebbe solo il valore di occupato in memoria, con conseguente deadlock. Dichiarare la bandiera occupata come volatile forza la lettura del suo valore prima di ogni test.

  

Riduce il rischio di errori di coerenza della memoria.

L'uso di variabili volatili riduce il rischio di errori di coerenza della memoria , poiché qualsiasi scrittura su una variabile volatile stabilisce un " succede-prima " con successive letture della stessa variabile. Ciò significa che le modifiche a una variabile volatile sono sempre visibili ad altri thread.

La tecnica di lettura, scrittura senza errori di coerenza della memoria è chiamata azione atomica .

Un'azione atomica è un'azione che si verifica effettivamente in una volta. Un'azione atomica non può fermarsi nel mezzo: o accade completamente o non accade affatto. Nessun effetto collaterale di un'azione atomica è visibile fino al completamento dell'azione.

Di seguito sono riportate le azioni che puoi specificare che sono atomiche:

  • Le letture e le scritture sono atomiche per le variabili di riferimento e per la maggior parte variabili primitive (tutti i tipi tranne long e double).
  • Le letture e le scritture sono atomiche per tutte le variabili dichiarate volatili (comprese le variabili lunghe e doppie).

Cheers!

Le variabili volatili sono una sincronizzazione leggera. Quando è richiesta la visibilità dei dati più recenti tra tutti i thread e l'atomicità può essere compromessa, in tali situazioni è necessario preferire le variabili volatili. La lettura delle variabili volatili restituisce sempre la scrittura più recente eseguita da qualsiasi thread poiché non sono memorizzate nella cache né nei registri né nelle cache in cui altri processori non possono vedere. Volatile è Lock-Free. Uso volatile, quando lo scenario soddisfa i criteri di cui sopra.

Volatile segue.

1 > La lettura e la scrittura di variabili volatili da parte di thread diversi provengono sempre dalla memoria, non dalla cache del thread o dal registro CPU. Quindi ogni thread si occupa sempre dell'ultimo valore. 2 > Quando 2 thread diversi lavorano con la stessa istanza o variabili statiche nell'heap, uno potrebbe vedere le azioni dell'altro come fuori servizio. Vedi il blog di jeremy manson su questo. Ma volatile aiuta qui.

Il seguente codice completamente in esecuzione mostra come un numero di thread può essere eseguito in ordine predefinito e stampare output senza utilizzare la parola chiave sincronizzata.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

Per raggiungere questo obiettivo, possiamo usare il seguente codice corrente completo.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

Il seguente link github ha un readme, che fornisce una spiegazione adeguata. https://github.com/sankar4git/volatile_thread_ordering

Dalla documentazione di Oracle page , la necessità di volatilità sorge una variabile per risolvere problemi di coerenza della memoria:

  

L'uso di variabili volatili riduce il rischio di errori di coerenza della memoria, poiché qualsiasi scrittura su una variabile volatile stabilisce una relazione accidentale prima delle successive letture della stessa variabile.

Ciò significa che le modifiche a una variabile volatile sono sempre visibili ad altri thread. Significa anche che quando un thread legge una variabile volatile, vede non solo l'ultima modifica al volatile , ma anche gli effetti collaterali del codice che ha portato alla modifica.

Come spiegato nella risposta Peter Parker , in assenza del modificatore volatile , lo stack di ogni thread può avere la propria copia della variabile. Rendendo la variabile come volatile , i problemi di coerenza della memoria sono stati risolti.

Dai un'occhiata alla jenkov per una migliore comprensione.

Dai un'occhiata alla relativa domanda SE per ulteriori dettagli su volatile & amp; usa casi per usare volatile:

Differenza tra volatile e sincronizzata in Java

Un caso d'uso pratico:

Hai molti thread, che devono stampare l'ora corrente in un particolare formato, ad esempio: java.text.SimpleDateFormat (" HH-mm-ss ") . Yon può avere una classe, che converte l'ora corrente in SimpleDateFormat e aggiorna la variabile per ogni secondo. Tutti gli altri thread possono semplicemente utilizzare questa variabile volatile per stampare l'ora corrente nei file di registro.

Una variabile volatile viene modificata in modo asincrono eseguendo contemporaneamente thread in un'applicazione Java. Non è consentito avere una copia locale di una variabile diversa dal valore attualmente presente in " main " memoria. In effetti, una variabile dichiarata volatile deve avere i suoi dati sincronizzati su tutti i thread, in modo che ogni volta che accedi o aggiorni la variabile in qualsiasi thread, tutti gli altri thread vedono immediatamente lo stesso valore. Ovviamente, è probabile che le variabili volatili abbiano un accesso superiore e un overhead di aggiornamento di "semplice". variabili, poiché il motivo per cui i thread possono avere una propria copia di dati è per una migliore efficienza.

Quando un campo viene dichiarato volatile, il compilatore e il runtime vengono avvisati che questa variabile è condivisa e che le operazioni su di essa non devono essere riordinate con altre operazioni di memoria. Le variabili volatili non vengono memorizzate nella cache nei registri o nelle cache in cui si trovano nascosto da altri processori, quindi la lettura di una variabile volatile restituisce sempre la scrittura più recente di qualsiasi thread.

per riferimento, fare riferimento a questo http: // techno -terminal.blogspot.in/2015/11/what-are-volatile-variables.html

La chiave volatile quando usata con una variabile, farà in modo che i thread che leggono questa variabile vedano lo stesso valore. Ora se hai più thread che leggono e scrivono su una variabile, rendere la variabile volatile non sarà sufficiente e i dati saranno danneggiati. I thread di immagini hanno letto lo stesso valore ma ognuno ha eseguito alcuni chase (diciamo un contatore incrementato), quando si riscrive in memoria, l'integrità dei dati viene violata. Questo è il motivo per cui è necessario sincronizzare la variabile (sono possibili modi diversi)

Se le modifiche sono fatte da 1 thread e gli altri devono solo leggere questo valore, la volatile sarà adatta.

Mi piace la spiegazione di Jenkov :

  

La parola chiave Java volatile viene utilizzata per contrassegnare una variabile Java come "archiviata nella memoria principale". Più precisamente ciò significa che ogni lettura di una variabile volatile verrà letta dalla memoria principale del computer e non dalla cache della CPU e che ogni scrittura su una variabile volatile verrà scritta nella memoria principale e non solo nella cache della CPU .

     

In realtà, da Java 5 la parola chiave volatile garantisce molto di più   le variabili volatili vengono scritte e lette dalla memoria principale.

È una garanzia di visibilità estesa, la cosiddetta garanzia prima di accadere.

  

Considerazioni sul rendimento di volatili

     

La lettura e la scrittura di variabili volatili provoca la lettura o la scrittura della variabile nella memoria principale. Leggere e scrivere nella memoria principale è più costoso dell'accesso alla cache della CPU. L'accesso alle variabili volatili impedisce anche il riordino delle istruzioni, che è una normale tecnica di miglioramento delle prestazioni. Pertanto, dovresti usare le variabili volatili solo quando devi davvero rafforzare la visibilità delle variabili.

la variabile volatile viene sostanzialmente utilizzata per l'aggiornamento istantaneo (flush) nella riga principale della cache condivisa dopo l'aggiornamento, in modo che le modifiche si riflettano immediatamente su tutti i thread di lavoro.

Di seguito è riportato un codice molto semplice per dimostrare il requisito di volatile per la variabile che viene utilizzato per controllare l'esecuzione del thread da un altro thread (questo è uno scenario in cui volatile è richiesto).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

Quando volatile non viene utilizzato: non vedrai mai il messaggio ' Interrotto: xxx ' anche dopo ' Interruzione : xxx "e il programma continua a essere eseguito.

Stopping on: 1895303906650500

Quando volatile viene utilizzato: vedrai immediatamente " Interrotto: xxx ".

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

Demo: https://repl.it/repls/SilverAgonizingObjectcode

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top