Perché i++ non atomica?

Perché i++ non atomica in Java?

Per ottenere un po ‘ più in profondità in Java ho provato a contare quante volte il ciclo nel thread vengono eseguiti.

Così ho usato un

private static int total = 0;

nella classe principale.

Ho due thread.

  • Filettatura 1: Stampe System.out.println("Hello from Thread 1!");
  • Filettatura 2: Stampe System.out.println("Hello from Thread 2!");

E contare le righe stampato il filo 1 e 2 thread. Ma le linee di filo 1 + linee di thread 2 non corrisponde al numero totale di righe stampate.

Ecco il mio codice:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {

    private static int total = 0;
    private static int countT1 = 0;
    private static int countT2 = 0;
    private boolean run = true;

    public Test() {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        newCachedThreadPool.execute(t1);
        newCachedThreadPool.execute(t2);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        run = false;
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println((countT1 + countT2 + " == " + total));
    }

    private Runnable t1 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT1++;
                System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    private Runnable t2 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT2++;
                System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    public static void main(String[] args) {
        new Test();
    }
}
  • Perché non provare con AtomicInteger?
  • Vedi anche: un altro MODO domanda, ++ non è considerato atomic, la Concorrenza in Java.
  • perché dici AtomicInteger non è atomico? È possibile utilizzare il getAndIncrement metodo per fare questo. È atomica.
  • la JVM è un iinc operazione per incrementare i numeri interi, ma che funziona solo per le variabili locali, in cui la concorrenza non è un problema. Per i campi, il compilatore genera read-modify-write comandi separatamente.
  • Perché ancora aspetta di essere atomica?
  • Anche su hardware che implementa un “incremento di posizione di archiviazione” istruzione, non c’è alcuna garanzia che questo è thread-safe. Solo perché un’operazione può essere rappresentato come un singolo operatore, non dice nulla su di esso, il filo di sicurezza.
  • Freak: anche se ci fosse un iinc istruzione per i campi, avere una sola istruzione non garantisce l’atomicità, ad esempio, nonvolatile long e double campo l’accesso non è garantito per essere atomica, a prescindere dal fatto che essa viene effettuata da un unico bytecode istruzione.

InformationsquelleAutor Andie2302 | 2014-08-06



9 Replies
  1. 119

    i++ probabilmente non è atomica in Java perché atomicità è un requisito speciale che non è presente nella maggior parte degli usi di i++. Tale requisito ha un notevole sovraccarico: c’è un costo di grandi nel fare un’operazione di incremento atomica; essa comporta la sincronizzazione sia il software e l’hardware livelli che non hanno bisogno di essere presenti in un normale incremento.

    Si potrebbe fare l’argomento che i++ dovrebbe essere stato progettato e documentato come in particolare l’esecuzione di un’atomica incremento, in modo che un non-atomica incremento viene eseguita utilizzando i = i + 1. Tuttavia, questo potrebbe rompere l’culturale “compatibilità” tra Java, C e C++. Così, toglierebbe una comoda notazione che i programmatori hanno familiarità con linguaggi del tipo di C per scontato, dando un significato speciale che si applica solo in circostanze limitate.

    Base di codice C o C++ come for (i = 0; i < LIMIT; i++) tradurrebbe in Java come for (i = 0; i < LIMIT; i = i + 1); perché sarebbe inappropriato usare l’atomica i++. Quel che è peggio, i programmatori provenienti da C o altri linguaggi del tipo di C a Java utilizzare i++ comunque, con conseguente inutile l’uso dell’atomica istruzioni.

    Anche al set di istruzioni della macchina, con un incremento del tipo di operazione non è di solito atomica per motivi di prestazioni. In x86, un’istruzione speciale “blocco prefisso” deve essere utilizzato per rendere il inc istruzione atomica: per gli stessi motivi di cui sopra. Se inc erano sempre atomica, non sarebbe mai essere usati quando non atomic inc è richiesto; i programmatori e i compilatori avrebbero generare il codice per il caricamento, si aggiunge 1 e negozi, perché sarebbe molto più veloce.

    In alcuni set di istruzioni architetture, non c’è atomica inc o forse no inc a tutti; per fare un atomic inc su MIPS, è necessario scrivere un software loop che utilizza il ll e sc: carico collegato, e archivio-condizionale. Carico collegato legge la parola, e archivio-condizionale memorizza il nuovo valore se la parola non è cambiato, o altro non riesce (che viene rilevato e provoca una ri-provare).

    • java non ha i puntatori, incrementando le variabili locali è intrinsecamente thread salvare, quindi con passanti il problema per lo più non sarebbe così male. vostro punto di minor sorpresa sta, naturalmente. inoltre, come è, i = i + 1 sarebbe una traduzione per ++i, non i++
    • La prima parola di domanda è “perché”. Ora come ora, questa è l’unica risposta per affrontare il problema del “perché”. Le altre risposte davvero solo riproporre la questione. Quindi +1.
    • Potrebbe essere la pena di notare che un atomicità garanzia non risolverebbe il problema di visibilità per gli aggiornamenti nonvolatile campi. Quindi, a meno che non si tratterà in ogni campo, come implicitamente volatile una volta che un thread è utilizzato il ++ operatore, ad un atomicità garanzia non risolverebbe concomitanti problemi di aggiornamento. E allora perché potenzialmente sprecare prestazioni per qualcosa, se non a risolvere il problema.
    • non è meglio dire ++? 😉
  2. 33

    i++ comporta due operazioni :

    1. leggere il valore della corrente di i
    2. incrementare il valore e assegnarlo a i

    Quando due thread eseguire i++ la stessa variabile, al tempo stesso, essi possono avere lo stesso valore di corrente di i, e quindi di incremento e di impostare i+1, così avrai un unico incremento invece di due.

    Esempio :

    int i = 5;
    Thread 1 : i++;
               //reads value 5
    Thread 2 : i++;
               //reads value 5
    Thread 1 : //increments i to 6
    Thread 2 : //increments i to 6
               //i == 6 instead of 7
    • (Anche se i++ è atomica, non sarebbe ben definita/thread-safe comportamento).
    • +1, ma “1. Una, 2. B e C” suona come tre operazioni, non due. 🙂
    • Si noti che, anche se l’operazione sono stati implementati con una singola istruzione macchina che viene incrementato di una posizione di archiviazione in luogo, non vi è alcuna garanzia che vorresti essere thread-safe. La macchina deve ancora recuperare il valore, l’incremento e memorizzare di nuovo, più ci possono essere più di cache di copie di quel percorso di archiviazione.
    • Non necessariamente. Se l’operazione di recupero, di incremento e di archivio vengono eseguite in modo atomico, o in una “transazione” (a livello CPU), quindi è presumibilmente thread-safe. Anche se, sì, ci possono essere le copie cache, ma che è una storia diversa. Penso che l’unica possibile contesa sarebbe se l’incremento thread erano in pausa (a causa del kernel di pianificazione) prima che l’operazione di incremento completato, eventualmente consentendo un altro thread per leggere la variabile. Io penso che dipende dal tipo di filettatura attuazione, OS e CPU.
    • Se due processori eseguire la stessa operazione con la stessa posizione di archiviazione contemporaneamente, e non vi è alcuna “riserva” in onda su la posizione, poi sarà quasi certamente interferire e di produrre risultati fasulli. Sì, è possibile che questa operazione per essere “sicuro”, ma richiede particolare impegno, anche a livello hardware.
    • Ma penso che la domanda era “Perché” e non “Quello che succede”.
    • Ho interpretato la domanda in modo diverso, dal momento che se l’op voleva sapere perché java creatori hanno scelto di non fare ++ atomica, non c’era bisogno di tutto il codice inviato. Il codice implicita ci hanno chiesto perché il codice si comporta il modo in cui funziona. Basato sul accettati risposta io possa aver sbagliato.
    • Grazie; tuttavia, credo, che: 1) lettura corrente; 2)incremento; 3) di assegnare di nuovo è ancora in tre operazioni 🙂

  3. 11

    La cosa importante è la JLS (Java Language Specification), piuttosto che come le varie implementazioni della JVM può o non può avere implementato una certa caratteristica del linguaggio. I JLS definisce i ++ postfix operatore clausola 15.14.2 che dice io.a. “il valore 1, è aggiunto il valore della variabile e la somma viene memorizzato nella variabile”. Nulla fa menzione o suggerimento al multithreading o atomicità. Per questi il JLS fornisce volatile e sincronizzato. Inoltre, c’è il pacchetto java.util.simultanee.atomic (vedi http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html)

  4. 5

    Perché i++ non atomica in Java?

    Rompere l’operazione di incremento in più istruzioni:

    Filettatura 1 & 2 :

    1. Recuperare il valore del totale dalla memoria
    2. Aggiungere 1 al valore
    3. Scrittura alla memoria

    Se non c’è la sincronizzazione, quindi diciamo Thread si è letto il valore 3 e incrementato a 4, ma non ha scritto di nuovo. A questo punto, il cambio di contesto succede. Infilare i due si legge il valore 3, incrementi e il cambio di contesto succede. Anche se entrambi i thread hanno incrementato il valore totale, sarà ancora 4 – race condition.

    • Non capisco come questo dovrebbe essere una risposta alla domanda. Una lingua può definire qualsiasi funzionalità come atomic, sia incrementi o unicorni. Appena esemplificare una conseguenza di atomic.
    • Sì una lingua può definire qualsiasi funzionalità come atomic, ma per quanto riguarda java è considerato operatore di incremento(che è la domanda inviato da OP) non è atomico e la mia risposta stati i motivi.
    • (scusa per il mio tono duro nel primo commento) Ma poi, il motivo sembra essere: “perché se sarebbe atomica, allora non ci sarebbero condizioni di gara”. I. e., sembra come se una condizione di competizione è auspicabile.
    • l’overhead introdotto per tenere un incremento di atomic è enorme e raramente, mantenendo il funzionamento del mercato e di conseguenza non atomic è auspicabile la maggior parte del tempo.
    • Nota che non metto in discussione i fatti, ma il ragionamento in questa risposta. Sostanzialmente dice “i++ non è atomico in Java a causa delle condizioni di gara si è”, che è come dire “un auto non ha conducente a causa degli incidenti che possono accadere” o “si ottiene non con il coltello, il currywurst-ordine, perché la wurst, potrebbe essere necessario tagliare”. Quindi, non penso che questa è una risposta. La domanda non era “che Cosa ho++ fare?” o “che Cosa è la conseguenza di i++ non essere sincronizzati?”.
  5. 4

    i++ è un’affermazione che implica semplicemente 3 operazioni:

    1. Leggere il valore della corrente di
    2. Scrivere il nuovo valore
    3. Memorizzare il nuovo valore

    Queste tre operazioni non sono pensati per essere eseguiti in un unico passaggio, in altre parole i++ non è un composto operazione. Come risultato, tutti i tipi di cose possono andare male quando più thread sono coinvolti in un singolo ma non composti operazione.

    Come un esempio immaginate questo scenario:

    Tempo di 1:

    Thread A fetches i
    Thread B fetches i

    Tempo 2:

    Thread A overwrites i with a new value say -foo-
    Thread B overwrites i with a new value say -bar-
    Thread B stores -bar- in i
    
    //At this time thread B seems to be more 'active'. Not only does it overwrite 
    //its local copy of i but also makes it in time to store -bar- back to 
    //'main' memory (i)

    Tempo 3:

    Thread A attempts to store -foo- in memory effectively overwriting the -bar- 
    value (in i) which was just stored by thread B in Time 2.
    
    Thread B has nothing to do here. Its work was done by Time 2. However it was 
    all for nothing as -bar- was eventually overwritten by another thread.

    E c’è l’hanno. Una condizione di competizione.


    Ecco perché i++ non è atomico. Se così fosse, niente di tutto questo sarebbe successo, e ogni fetch-update-store accadrebbe in modo atomico. Che è esattamente quello che AtomicInteger e nel tuo caso sarebbe probabilmente adatta a destra dentro.

    P. S.

    Un libro eccellente che copre tutti quei problemi e poi alcuni è questo:
    Java Concurrency in Pratica

    • Hmm. Una lingua può definire qualsiasi funzionalità come atomic, sia incrementi o unicorni. Appena esemplificare una conseguenza di atomic.
    • Esattamente. Ma vorrei anche sottolineare che non è una singola operazione che per estensione implica che il costo computazionale per la tornitura di più tali operazioni atomica è molto più costoso che a sua volta -che in parte giustifica perché i++ non è atomico.
    • Mentre ottenere il punto, la tua risposta è un po ‘ di confusione per l’apprendimento. Vedo un esempio, e una conclusione che dice “a causa della situazione nell’esempio”; imho questo è un ragionamento incompleto 🙁
    • Forse non il più pedagogico risposta, ma è il meglio che posso offrire attualmente. Spero che possa aiutare le persone e non confondere. Grazie per la critica, tuttavia. Proverò ad essere più preciso nel mio post futuri.
  6. 2

    Se l’operazione i++ sarebbe atomica non avresti la possibilità di leggere il valore da esso. Questo è esattamente ciò che si desidera fare uso di i++ (invece di usare ++i).

    Per esempio, vediamo il seguente codice:

    public static void main(final String[] args) {
        int i = 0;
        System.out.println(i++);
    }

    In questo caso bisogna aspettare l’uscita di: 0
    (perché siamo in post incremento, ad esempio, prima leggi, poi aggiornamento)

    Questo è uno dei motivi che l’operazione non può essere atomica, perché è necessario leggere il valore (e di fare qualcosa con esso) e poi aggiornare il valore.

    L’altra ragione importante è che qualcosa di atomico di solito prende più tempo a causa di chiusura. Sarebbe sciocco per avere tutte le operazioni primitive di prendere un po ‘ di più per i rari casi in cui le persone vogliono avere operazioni atomiche. È per questo che ho aggiunto AtomicInteger e altri atomica classi di lingua.

    • Questo è fuorviante. Si devono separare esecuzione e di ottenere il risultato, altrimenti non si poteva ottenere valori da qualsiasi operazione atomica.
    • No non lo è, che è il motivo per cui Java AtomicInteger ha un get(), getAndIncrement(), getAndDecrement(), incrementAndGet(), decrementAndGet (), etc.
    • E il linguaggio Java può definire i++ per essere estesa a i.getAndIncrement(). Tale espansione non è nuovo. E. g., le espressioni lambda in C++ vengono espansi anonimo definizioni di classe in C++.
    • Dato atomico i++ si può banalmente creare un’atomica ++i o viceversa. Uno è equivalente alle altre più uno.
  7. 2

    Ci sono due passaggi:

    1. fetch mi da memoria
    2. impostare i+1 per i

    quindi non è operazione atomica.
    Quando thread1 esegue i++, e thread2 esegue i++, il valore finale dell’io possono essere i+1.

  8. -1

    Concorrenza (il Thread di classe e simili), è una caratteristica aggiunta in v1.0 di Java. i++ è stata aggiunta nella beta, e, come tale, è più che probabile che nella sua (più o meno) implementazione originale.

    Spetta al programmatore per sincronizzare le variabili. Check out Oracle tutorial su questo.

    Edit: Per chiarire, ho++ è ben definito il procedimento che precede Java, e, come tale, i progettisti di Java ha deciso di mantenere la funzionalità originale di tale procedura.

    I ++ operatore è stato definito in B (1969) che precede java e filettatura da appena un po’.

    • -1 “public class Thread … Da: JDK1.0” Fonte: docs.oracle.com/javase/7/docs/api/index.html?java/lang/…
    • La versione non importa tanto quanto il fatto che è stato ancora attuato, prima della classe Thread e non è stato modificato a causa di esso, ma ho modificato la mia risposta per favore.
    • Ciò che conta è che la tua affermazione “non è stato ancora attuato, prima della classe Thread” non è supportata da fonti. i++ non atomic è una decisione, non una svista in un sistema di coltivazione.
    • Lol che è carino. ho++ è stato definito bene prima di Thread, semplicemente perché non c’erano le lingue che esisteva prima di Java. I creatori di Java utilizzato quelli di altre lingue come base invece di ridefinire un ben accettato procedura. Dove ho mai detto è stata una svista?
    • Ecco alcune fonti che mostrano come il vecchio ++ è: en.wikipedia.org/wiki/Increment_and_decrement_operators en.wikipedia.org/wiki/B_(programming_language)
    • Solo perché una caratteristica del linguaggio preso a prestito o ispirati da una funzione corrispondente in altre lingue non significa necessariamente mantiene le stesse esatte caratteristiche di fondo. Si consideri, ad esempio, la varietà di lambda funzioni in diverse lingue (ad esempio, la debolezza di Python lambda rispetto al vero Lisp lambda symbo1ics.com/blog/?p=1292). Anche in C++, alcuni tratti dal C non sono abbastanza identici alle loro controparti C, nonostante il fatto che il C++ è spesso pensato per essere compatibile con C (cprogramming.com/tutorial/c-vs-c++.html).
    • Ma molti in prestito le parole chiave e operatori non implementato nella/e lingua / s sono state prese in prestito da. E. g., class e operator. in C++ sono totalmente ortogonali al loro Java “equivalenti”.

Lascia un commento