2016-12-11 3 views
2

Je voudrais comprendre ce qui se passe du point de vue du cache (protocole MESI) si un programmeur oublie d'ajouter le mot-clé volatile à une variable utilisée pour la synchronisation.Synchronisation brisée sans variable volatile

L'extrait de code suivant s'arrête simplement après quelques itérations. Je sais que c'est parce que l'un des 2 threads (supposons THREAD 0) ne voit pas la mise à jour faite à current variable par THREAD 1 fait par getNext(), donc il continue à boucler pour toujours.

Cependant, je ne comprends pas pourquoi c'est le cas. THREAD 0 est en boucle sur current et devrait voir à un moment donné que la ligne de cache a été mise à jour par THREAD 1 (la ligne de cache est passée en état Modified) et émettre un message "Read" sur le bus mémoire pour le récupérer dans son cache local. L'ajout du modificateur volatile à la variable current fera en sorte que tout fonctionne correctement.

Que se passe-t-il qui empêche THREAD 0 de continuer son exécution?

Référence: Memory Barriers: a Hardware View for Software Hackers

public class Volatile { 
    public static final int THREAD_0 = 0; 
    public static final int THREAD_1 = 1; 
    public static int current; 
    public static int counter = 0; 

    public static void main(String[] args) { 
     current = 0; 

     /** Thread 0 **/ 
     Thread thread0 = new Thread(() -> { 
      while(true) { /** THREAD_0 */ 
       while (current != THREAD_0); 

       counter++; 
       System.out.println("Thread0:" + counter); 

       current = getNext(THREAD_0); 
      } 
     }); 

     /** Thread 1 **/ 
     Thread thread1 = new Thread(() -> { 
      while(true) { /** THREAD_1 */ 
       while (current != THREAD_1); 

       counter++; 
       System.out.println("Thread1:" + counter); 

       current = getNext(THREAD_1); 
      } 
     }); 

     thread0.start(); 
     thread1.start(); 
    } 

    public static int getNext(int threadId) { 
     return threadId == THREAD_0 ? THREAD_1 : THREAD_0; 
    } 
} 

Répondre

0

architectures de processeurs différents peuvent avoir différents degrés de cohérence de cache. Certains peuvent être assez minimes, car le but est de maximiser le débit et de synchroniser les caches avec de la mémoire inutilement. Java impose ses propres barrières de mémoire lorsqu'il détecte que la coordination est requise, mais cela dépend du programmeur suivant les règles. Si le programmeur n'ajoute pas d'indications (comme utiliser les mots-clés synchronisés ou volatiles) qu'une variable peut être partagée entre les threads, le JIT peut supposer qu'aucun autre thread ne modifie cette variable, et il peut optimiser en conséquence . Le bytecode qui teste cette variable peut être réorganisé de façon drastique. Si le JIT réordonne le code assez alors quand le matériel détecte une modification et récupère la nouvelle valeur peut ne pas importer.

Je cite Java dans la pratique 3.1 Concurrency:

En l'absence de synchronisation, le compilateur, le processeur et l'exécution peut faire des choses carrément bizarres à l'ordre dans lequel les opérations semblent exécuter. Les tentatives de raisonner sur l'ordre dans lequel les actions de mémoire "doivent" se produire dans des programmes multithread insuffisamment synchronisés seront presque certainement incorrectes.

(Il y a plusieurs endroits dans le livre JCIP plaidant que le raisonnement sur le code insuffisamment synchronisé est futile.)

+1

Vous êtes tout à fait raison, ce problème est lié au compilateur JIT. Lancer le programme en mode interprété uniquement (-Xint) le fera fonctionner pour toujours alors qu'en mode compilé (-Xcomp) il ne fonctionne tout simplement pas. Merci pour cette réponse! –