Pourquoi est i++ pas atomique?
Pourquoi est i++
pas atomique en Java?
Pour obtenir un peu plus profondément en Java, j'ai essayé de compter combien de fois la boucle dans les threads sont exécutées.
J'ai donc utilisé une
private static int total = 0;
dans la classe principale.
J'ai deux fils.
- Thread 1: Imprime
System.out.println("Hello from Thread 1!");
- Filetage 2: Imprime
System.out.println("Hello from Thread 2!");
Et je compte les lignes imprimées en 1 fil et du fil 2. Mais les lignes de fil 1 + lignes de fil 2 ne correspondent pas au nombre total de lignes imprimées.
Voici mon code:
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();
}
}
- Pourquoi n'essayez-vous pas avec
AtomicInteger
? - Voir aussi: l'autre DONC, la question, ++ pas considéré atomique, Simultanéité en Java.
- pourquoi dites-vous AtomicInteger n'est pas atomique? Vous pouvez utiliser la getAndIncrement la méthode pour ce faire. Il est atomique.
- la JVM a un
iinc
opération d'incrémentation de nombres entiers, mais qui ne fonctionne que pour les variables locales, où la simultanéité n'est pas un sujet de préoccupation. Pour les champs, le compilateur génère de lecture-modification-écriture séparément. - Pourquoi voudriez-vous même s'attendre à être atomique?
- Même sur le matériel qui met en œuvre un "incrémenter emplacement de stockage" de l'enseignement, il n'y a aucune garantie que c'est thread-safe. Tout simplement parce qu'une opération peut être représenté comme un seul opérateur ne dit rien à ce sujet un fil de sécurité.
- Freak: même si il y avait un
iinc
instruction pour les champs, ayant une seule instruction ne permet pas de garantir l'atomicité, par exemple, la non-volatile
long
etdouble
d'accès sur le terrain en ne garantissant pas être atomique, indépendamment du fait qu'elle est effectuée par une seule instruction bytecode.
Vous devez vous connecter pour publier un commentaire.
i++
est probablement pas atomique en Java, car l'atomicité est une exigence particulière, qui n'est pas présent dans la majorité des utilisations dei++
. Cette exigence a une surcharge importante: il y a un gros coût dans la réalisation d'une opération d'incrémentation atomique; il s'agit d'une synchronisation à la fois le logiciel et le matériel des niveaux qui n'ont pas besoin d'être présent dans une simple incrémentation.Vous pourriez faire l'argument que
i++
doivent avoir été conçus et documentés comme spécifiquement de l'exécution d'une atomique incrément, de sorte qu'un non-atomique de l'incrément est effectuée à l'aidei = i + 1
. Cependant, ce serait briser le "compatibilité culturelle" entre le Java et le C et le C++. Ainsi, il serait enlever une pratique de la notation qui les programmeurs familier avec le C-comme les langages de prendre pour acquis, à qui il donne une signification particulière ne s'applique que dans des circonstances limitées.De base code C ou C++ comme
for (i = 0; i < LIMIT; i++)
serait de traduire en Java commefor (i = 0; i < LIMIT; i = i + 1)
; car il serait inapproprié d'utiliser la atomiquei++
. Ce qui est pire, les programmeurs venant de C ou d'autres C-comme les langages Java serait d'utiliseri++
de toute façon, résultant de l'utilisation inutile des instructions atomiques.Même à la machine à jeu d'instruction niveau, un type d'incrément de l'opération n'est généralement pas atomique pour des raisons de performances. En x86, une instruction spéciale "préfixe lock" doit être utilisé pour faire de la
inc
instruction atomique: pour les mêmes raisons que ci-dessus. Siinc
ont toujours été atomique, il n'y aurait jamais être utilisé en cas de non-atomique inc est nécessaire; les programmeurs et les compilateurs serait de générer du code qui charge, ajoute 1 et les magasins, car il serait beaucoup plus rapide.Dans certains jeu d'instructions architectures, il n'est pas atomique
inc
ou peut-être pasinc
à tous; faire atomique inc sur MIPS, vous devez écrire une boucle du logiciel qui utilise lell
etsc
: charge liée, et de les stocker en liberté sous condition. La charge liée lit le mot, et de les stocker en liberté sous condition stocke la nouvelle valeur si le mot n'a pas changé, sinon elle échoue (ce qui est détecté et provoque une ré-essayer).i = i + 1
serait une traduction pour++i
, pasi++
volatile
champs. Donc, à moins que vous ne traiter chaque champ implicitementvolatile
une fois qu'un thread a utilisé le++
de l'opérateur, par exemple une atomicité de garantie ne résoudrait pas simultanées problèmes de mise à jour. Pourquoi donc potentiellement perdre de performance pour quelque chose si ça ne résout pas le problème.++
? 😉i++
implique deux opérations :i
i
Lorsque deux threads effectuer
i++
sur la même variable dans le même temps, ils peuvent obtenir la même valeur de courant dei
, et puis incrémenter et mis ài+1
, de sorte que vous aurez une simple incrémentation au lieu de deux.Exemple :
i++
est atomique, il ne serait pas bien défini/thread-safe comportement.)La chose la plus importante est la JLS (Java Language Specification) plutôt que la façon dont les différentes implémentations de la JVM peut ou peut ne pas avoir mis en oeuvre un certain fonction de la langue. Le JLS définit le ++ postfix opérateur dans la clause 15.14.2 qui dit je.un. "la valeur 1 est ajoutée à la valeur de la variable et la somme est stocké dans la variable". Nulle part il n'est mention ou allusion à multithreading ou de l'atomicité. Pour ces JL fournit volatils et synchronisé. En outre, il existe le package java.util.de façon concomitante.atomique (voir http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html)
Brisons l'opération d'incrémentation en plusieurs états:
Thread 1 & 2 :
Si il n'y a pas de synchronisation alors disons Thread on a lu la valeur 3 et incrémenté à 4, mais n'a pas écrit, il est revenu. À ce stade, le changement de contexte se produit. Enfiler deux lit la valeur 3, il incrémente et le changement de contexte se produit. Bien que les deux threads ont incrémenté de la valeur totale, il sera toujours 4 - condition de course.
i++
est un énoncé qui implique de 3 opérations:Ces trois opérations ne sont pas destinés à être exécutés en une seule étape ou en d'autres termes
i++
n'est pas un composé opération. En conséquence, toutes sortes de choses peuvent aller mal lorsque plusieurs threads sont impliqués dans un seul, mais non des composés de l'opération.Envisagez le scénario suivant:
Temps 1:
Temps 2:
Temps 3:
Et là vous l'avez. Une condition de concurrence.
C'est pourquoi
i++
n'est pas atomique. S'il l'était, rien de tout cela ne serait arrivé et chaquefetch-update-store
arriverait atomiquement. C'est exactement ce queAtomicInteger
est pour et dans votre cas, il serait sans doute mieux.P. S.
Un excellent livre, à la couverture de toutes ces questions, et puis certains est ceci:
Java de la Simultanéité dans la Pratique
i++
n'est pas atomique.Dans la JVM, une augmentation implique une lecture et une écriture, de sorte qu'il n'est pas atomique.
Si l'opération
i++
serait atomique, vous n'auriez pas la chance de lire la valeur de celui-ci. C'est exactement ce que vous voulez faire à l'aide dei++
(au lieu d'utiliser++i
).Par exemple, regardez le code suivant:
Dans ce cas, nous nous attendons à la sortie:
0
(parce que nous post incrémentation, par exemple, de lire d'abord, puis mise à jour)
C'est l'une des raisons de l'opération ne peut pas être atomique, parce que vous avez besoin de lire la valeur (et en faire quelque chose) et puis mise à jour de la valeur.
L'autre raison importante est que de faire quelque chose atomiquement généralement prend plus de temps à cause de verrouillage. Il serait ridicule d'avoir toutes les opérations sur les primitives de prendre un peu de temps pour les rares cas où les gens veulent avoir des opérations atomiques. C'est pourquoi ils ont ajouté des
AtomicInteger
et d'autres atomique de classes à la langue.i++
être étendu à d'i.getAndIncrement()
. Une telle expansion n'est pas nouveau. E. g., les lambdas en C++ sont développés pour anonyme des définitions de classe en C++.i++
on peut trivialement créer atomique++i
ou vice-versa. L'un équivaut à l'autre plus un.Il y a deux étapes:
donc c'est pas une opération atomique.
Lorsque thread1 exécute i++, et thread2 exécute i++, la valeur finale de j'ai peut-être i+1.
De la concurrence (le
Thread
classe et tel) est une fonction ajoutée dans v1.0 de Java.i++
a été ajouté dans la version bêta avant, et en tant que tel il est encore plus probable dans son (plus ou moins) la mise en œuvre d'origine.C'est au programmeur de synchroniser les variables. Découvrez Oracle tutoriel sur ce.
Edit: Pour préciser, j'++ est une procédure bien définie qui est antérieure à Java, et en tant que tel, les concepteurs de Java ont décidé de garder la fonctionnalité d'origine de cette procédure.
L' ++ opérateur a été défini en B (1969), qui est antérieure à java et le filetage en juste un peu.
i++
pas être atomique est une décision de conception, pas une surveillance dans un système de culture.lambda
fonctions dans différentes langues (par exemple, la faiblesse de Pythonlambda
par rapport à la vraie Lisp lambdas symbo1ics.com/blog/?p=1292). Même en C++, certaines fonctionnalités prises à partir de C ne sont pas tout à fait identiques à leurs C homologues, malgré le fait que le C++ est souvent pensé pour être rétro-compatible avec les C (cprogramming.com/tutorial/c-vs-c++.html).class
etoperator.
en C++ sont totalement orthogonal à leur Java "équivalents".