Est-ce que id = 1 – id atomic?

À partir de la page 291 des examens pratiques du programmeur OCP Java SE 6, question 25:

public class Stone implements Runnable { static int id = 1; public void run() { id = 1 - id; if (id == 0) pick(); else release(); } private static synchronized void pick() { System.out.print("P "); System.out.print("Q "); } private synchronized void release() { System.out.print("R "); System.out.print("S "); } public static void main(Ssortingng[] args) { Stone st = new Stone(); new Thread(st).start(); new Thread(st).start(); } } 

Une des réponses est:

La sortie pourrait être PQPQ

J’ai marqué cette réponse comme étant correcte. Mon raisonnement:

  1. Nous commençons deux threads.
  2. Le premier entre run() .
  3. Selon JLS 15.26.1 , il évalue d’abord 1 - id . Le résultat est 0 . Il est stocké sur la stack du thread. Nous sums sur le sharepoint sauver ce 0 à l’ id statique, mais …
  4. Boom, le programmateur choisit le deuxième thread à exécuter.
  5. Donc, le deuxième thread entre run() . L’ id statique est toujours 1 , donc il exécute la méthode pick() . PQ est imprimé.
  6. Le planificateur choisit le premier thread à exécuter. Il prend 0 de sa stack et enregistre dans un id statique. Ainsi, le premier thread exécute également pick() et imprime PQ .

Cependant, dans le livre, il est écrit que cette réponse est incorrecte:

Il est incorrect car la ligne id = 1 - id échange la valeur de id entre 0 et 1 . Il n’y a aucune chance que la même méthode soit exécutée deux fois.

Je ne suis pas d’accord Je pense qu’il y a une chance pour le scénario que j’ai présenté ci-dessus. Un tel échange n’est pas atomique. Ai-je tort?

Ai-je tort?

Non, vous avez tout à fait raison, comme votre exemple de chronologie.

En plus de ne pas être atomique, il n’est pas garanti que l’écriture sur l’ id sera captée par l’autre thread, étant donné qu’il n’ya pas de synchronisation et que le champ n’est pas volatile.

Il est quelque peu déconcertant que le matériel de référence comme celui-ci soit incorrect 🙁

À mon avis, la réponse aux examens pratiques est correcte. Dans ce code, vous exécutez deux threads qui ont access au même identifiant de variable statique. Les variables statiques sont stockées sur le tas en Java, pas sur la stack. L’ordre d’exécution des runnables est imprévisible.

Cependant, pour changer la valeur de id chaque thread:

  1. effectue une copie locale de la valeur stockée dans l’adresse mémoire de l’ID dans le registre du processeur;
  2. effectue l’opération 1 - id . Ssortingctement parlant, deux opérations sont effectuées ici (-id and +1) ;
  3. déplace le résultat dans l’espace mémoire de id sur le tas.

Cela signifie que même si la valeur de l’ID peut être modifiée simultanément par l’un des deux threads, seules les valeurs initiales et finales sont modifiables. Les valeurs intermédiaires ne seront pas modifiées les unes par les autres.

De plus, l’parsing du code peut montrer qu’à tout moment, id ne peut être que 0 ou 1.

Preuve:

  • Valeur initiale id = 1; Un thread le changera à 0 ( id = 1 - id ). Et l’autre thread le ramènera à 1.

  • Valeur initiale id = 0; Un thread le changera à 1 ( id = 1 - id ). Et l’autre thread le ramènera à 0.

Par conséquent, l’état de valeur de id est discret, soit 0 ou 1.

Fin de la preuve.

Il peut y avoir deux possibilités pour ce code:

  • Possibilité 1. Thread one accède d’abord à la variable id. Ensuite, la valeur de id ( id = 1 - id change à 0). Par la suite, seule la méthode pick () sera exécutée, en imprimant PQ . Le deuxième thread évaluera id à ce moment id = 0 ; la méthode release() sera alors impression exécutée R S. En conséquence, PQRS sera imprimé.

  • Possibilité 2. Thread deux accède d’abord à la variable id. Ensuite, la valeur de id ( id = 1 - id change à 0). Par la suite, seule la méthode pick () sera exécutée, en imprimant PQ . Le premier thread évaluera id à ce moment-là id = 0 ; la méthode release() sera alors impression exécutée R S. En conséquence, PQRS sera imprimé.

Il n’y a pas d’autres possibilités. Cependant, il convient de noter que des variantes de PQRS telles que PRQS ou RPQS , etc. peuvent être imprimées car pick() est une méthode statique et est donc partagée entre les deux threads. Cela conduit à l’exécution simultanée de cette méthode, ce qui pourrait entraîner l’impression des lettres dans un ordre différent en fonction de votre plate-forme.

Cependant, dans tous les cas, ni la méthode pick() ni release () seront exécutées deux fois car elles sont mutuellement exclusives . Par conséquent, PQPQ ne sera pas une sortie.