43 votes

Les ints primitifs de Java sont-ils atomiques par conception ou par accident?

Sont java primitive des entiers (int) atomique à tous, pour que la matière? Quelques essais avec les deux fils du partage d'un int, ce qui semble indiquer qu'ils sont, mais bien sûr l'absence de preuve qu'ils sont pas ne signifie pas qu'ils sont.

Plus précisément, le test que j'ai couru c'était ça:

public class IntSafeChecker {
    static int thing;
    static boolean keepWatching = true;

    // Watcher just looks for monotonically increasing values   
    static class Watcher extends Thread {
        public void run() {
            boolean hasBefore = false;
            int thingBefore = 0;

            while( keepWatching ) {
                // observe the shared int
                int thingNow = thing;
                // fake the 1st value to keep test happy
                if( hasBefore == false ) {
                    thingBefore = thingNow;
                    hasBefore = true;
                }
                // check for decreases (due to partially written values)
                if( thingNow < thingBefore ) {
                    System.err.println("MAJOR TROUBLE!");
                }
                thingBefore = thingNow;
            }
        }
    }

    // Modifier just counts the shared int up to 1 billion
    static class Modifier extends Thread {
        public void run() {
            int what = 0;
            for(int i = 0; i < 1000000000; ++i) {
                what += 1;
                thing = what;
            }
            // kill the watcher when done
            keepWatching = false;
        }
    }

    public static void main(String[] args) {
        Modifier m = new Modifier();
        Watcher w = new Watcher();
        m.start();
        w.start();
    }
}

(et c'était seulement essayé avec java jre 1.6.0_07 sur un windows 32 bits PC)

Essentiellement, le Modificateur écrit un nombre de la séquence de la salle de entier, tandis que l'Observateur vérifie que les valeurs observées ne jamais diminuer. Sur une machine où une valeur 32 bits devait être consulté en tant que de quatre octets (ou même deux mots de 16 bits), il y aurait une probabilité que l'Observateur attrape le partagé entier dans un incohérent, demi-état mis à jour, et de détecter la valeur de déclin plutôt que de l'augmenter. Cela devrait fonctionner si le (hypothétique) octets de données sont collectées/écrit LSB 1er ou le MSB 1er, mais est seulement probabiliste au mieux.

Il semble très probable, étant donné aujourd'hui à l'échelle chemins de données une valeur 32 bits peut être atomique, même si le java spec ne l'exige pas. En fait, avec la version 32 bits du bus de données, il semblerait que vous pourriez avoir à travailler plus fort pour obtenir de l'accès atomique d' octets que de 32 bits entiers.

Googling "primitif java thread de sécurité" se transforme des charges des trucs sur le thread-safe de classes et d'objets, mais la recherche de l'info sur les primitives semble être à la recherche de la proverbiale aiguille dans une botte de foin.

65voto

Jon Skeet Points 692016

Tous les accès à la mémoire en Java sont atomiques par défaut, à l'exception de l' long et double (ce qui peut être atomique, mais n'ont pas à l'être). Il n'est pas mis très clairement pour être honnête, mais je crois que c'est l'implication.

À partir de la section 17.4.3 de la JLS:

À l'intérieur d'une manière séquentielle cohérente l'exécution, il existe un ordre total sur toutes les actions individuelles (comme lit et écrit) ce qui est cohérent avec l'ordre du programme, et chaque l'action individuelle est atomique et est immédiatement visibles pour chaque thread.

et puis dans 17.7:

Certaines implémentations peuvent trouver commode de diviser une seule écriture action sur une version 64 bits de long ou double valeur en deux actions de l'écriture sur adjacent 32 bits. Pour des raisons d'efficacité, ce comportement est la mise en œuvre spécifique; virtuelle Java les machines sont libres d'effectuer des écritures de longue et double des valeurs atomiquement ou en deux parties.

Notez que l'atomicité est très différente de la volatilité des si.

Lorsqu'un thread mises à jour d'un nombre entier de 5, c'est la garantie qu'un autre thread ne verrez pas 1 ou 4 ou tout autre entre l'état, mais sans aucune explicite de la volatilité ou de verrouillage, l'autre thread pourrait voir les 0 à jamais.

Quant à travailler dur pour obtenir atomique accès à octets, vous avez raison: la VM peut bien avoir à essayer dur... mais il ne le faut. De l'article 17.6 de l'spec:

Certains processeurs ne fournissent pas l' capacité à écrire sur un seul octet. Il serait illégal de mettre en œuvre des octets tableau des mises à jour sur un tel processeur, par la simple lecture d'un mot entier, la mise à jour de la appropriée de l'octet, et puis écrire le mot en entier retour à de la mémoire. Ce problème est parfois connu comme mot de déchirure, et sur les processeurs qui ne peuvent pas facilement mettre à jour un seul octet dans l'isolement de certains autres l'approche sera nécessaire.

En d'autres termes, c'est à la JVM pour obtenir ce droit.

29voto

Robert Munteanu Points 31558
  • Aucun montant de test peut prouver la sécurité des threads - il ne peut réfuter c';
  • J'ai trouvé une référence indirecte dans JLS 17.7 laquelle les états

Certaines implémentations peuvent trouver commode de diviser une seule action de l'écriture sur une version 64 bits de long ou double de la valeur dans les deux actions de l'écriture sur les 32 bits.

et plus bas

Pour l'application du langage de programmation Java modèle de mémoire, une seule écriture à un non-volatile long ou double valeur est traitée comme deux écritures: une pour chacune des moitiés de 32 bits.

Cela semble impliquer que écrit à ints sont atomiques.

4voto

Michal B Points 41

Je pense que cela ne fonctionne pas comme prévu:

 private static int i = 0;
public void increment() {
   synchronized (i) { 
      i++; 
   }
}
 

entier est immuable, vous synchronisez donc constamment sur un objet différent. int "i" est placé automatiquement dans l'objet Integer, puis vous définissez le verrouillage sur celui-ci. Si un autre thread entre dans cette méthode, int i est automatiquement placé dans un autre objet Integer et vous définissez le verrouillage sur un autre objet avant.

4voto

James Points 1732

Une lecture ou d'écriture de type entier ou tout type plus petit doit être atomique, mais comme Robert l'a noté, longs et les doubles peuvent être ou non en fonction de la mise en œuvre. Cependant, toute opération qui utilise à la fois en lecture et en écriture, y compris tous les opérateurs d'incrémentation, ne sont pas atomiques. Ainsi, si vous avez des fils d'exploitation sur un entier i=0, on n'a++ i et l'autre i=10, le résultat pourrait être de 1, 10 ou 11.

Pour des opérations de ce genre, vous devriez regarder AtomicInteger qui a des méthodes pour atomiquement de la modification d'une valeur lors de la récupération de l'ancienne ou de atomiquement incrémenter la valeur.

Enfin, les threads peuvent mettre en cache la valeur de la variable et de ne pas voir les changements effectués à partir d'autres threads. Assurez-vous que les deux fils toujours voir les modifications apportées par l'autre thread, vous devez marquer la variable comme étant volatile.

4voto

Kounavi Points 528

Je suis d'accord avec Jon Skeet et je voudrais ajouter que beaucoup de gens confondent la notion d'atomicité, de volatilité et de la sécurité des threads parce que parfois les termes sont utilisés de façon interchangeable.
Par exemple, considérez ceci:

private static int i = 0;

Alors que quelqu'un peut arguer du fait que cette opération est atomique, l'hypothèse est fausse.
L'énoncé
effectue trois opérations:
1)Lire
2)mise à Jour
3)Écrire

Par conséquent, les threads qui opèrent sur cette variable doit être synchronisé comme ceci:

public void increment() { i++; }

ou ceci:

i++;

Notez que, pour une seule instance de l'objet, l'appel d'une méthode qui est accessible par plusieurs threads et fonctionner sur le partage de données mutable l'on doit prendre en considération le fait que l'un des paramètres de la méthode, la variable locale et de la valeur de retour sont locaux pour chaque Thread.

Pour plus d'informations, consultez ce lien:
http://www.javamex.com/tutorials/synchronization_volatile.shtml

Espérons que cette aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X