2008-11-07 4 views
3

Cette question a été discutée dans deux billets de blog (http://dow.ngra.de/2008/10/27/when-systemcurrenttimemillis-is-too-slow/, http://dow.ngra.de/2008/10/28/what-do-we-really-know-about-non-blocking-concurrency-in-java/), mais je n'ai pas encore entendu de réponse définitive. Si nous avons un fil qui fait ceci:Est-ce que cette façon de détecter les battements de cœur est sûre et cohérente?

public class HeartBeatThread extends Thread { 
    public static int counter = 0; 
    public static volatile int cacheFlush = 0; 

    public HeartBeatThread() { 
    setDaemon(true); 
    } 

    static { 
    new HeartBeatThread().start(); 
    } 

    public void run() { 
    while (true) {  
     try { 
     Thread.sleep(500); 
     } catch (InterruptedException e) { 
     throw new RuntimeException(e); 
     } 

     counter++; 
     cacheFlush++; 
    } 
    } 
} 

Et de nombreux clients qui exécutent ce qui suit:

if (counter == HeartBeatThread.counter) return; 
counter = HeartBeatThread.cacheFlush; 

est-il threadsafe ou non?

Répondre

5

Dans le modèle de mémoire java? Non, vous n'êtes pas d'accord.

J'ai vu un certain nombre de tentatives pour se diriger vers une approche très «flush» comme celle-ci, mais sans une clôture explicite, vous jouez certainement avec le feu.

Le 'arrive avant' dans la sémantique

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7

début à la référence à des actions purement inter-thread comme 'actions' à la fin de 17.4.2. Cela entraîne beaucoup de confusion car, avant cela, ils distinguaient les actions inter et intra- thread. Par conséquent, l'action intra-thread du compteur de manipulation n'est pas explicitement synchronisée à travers l'action volatile par la relation arrive-avant. Vous avez deux fils de raisonnement à suivre sur la synchronisation, l'un gouverne la cohérence locale et est soumis à toutes les astuces d'analyse des alias, etc pour mélanger les opérations L'autre concerne la cohérence globale et n'est défini que pour les opérations inter-thread. Un pour la logique intra-thread qui dit dans le thread les lectures et les écritures sont constamment réordonnés et un pour la logique inter-thread qui dit des choses comme des lectures/écritures volatiles et que les démarrages/fins de synchronisation sont correctement clôturés.

Le problème est que la visibilité de l'écriture non volatile n'est pas définie car il s'agit d'une opération intra-thread et donc non couverte par la spécification. Le processeur sur lequel il s'exécute devrait être capable de le voir comme vous avez exécuté ces instructions en série, mais sa séquentialisation à des fins inter-thread est potentiellement indéfini. Maintenant, la réalité de savoir si cela peut ou non vous affecter est une autre affaire entièrement.

Lors de l'exécution de Java sur les plates-formes x86 et x86-64?Techniquement vous êtes dans un territoire trouble, mais pratiquement les garanties x86 très fortes sur les lectures et écritures y compris l'ordre total sur la lecture/écriture à travers l'accès à cacheflush et l'ordre local sur les deux écritures et les deux lectures devraient activer ce code pour s'exécuter correctement à condition qu'il le fasse via le compilateur sans être inquiété. Cela suppose que le compilateur n'intervient pas et essaie d'utiliser la liberté autorisée par le standard pour réordonner les opérations sur vous en raison de l'absence d'alias prouvable entre les deux opérations intra-thread.

Si vous vous déplacez vers une mémoire avec une sémantique de release plus faible comme un ia64? Ensuite, vous êtes de retour par vous-même.

Un compilateur pourrait en toute bonne foi rompre ce programme en Java sur n'importe quelle plate-forme, cependant. Le fait qu'il fonctionne maintenant est un artefact des implémentations actuelles de la norme, pas de la norme. En outre, dans le CLR, le modèle d'exécution est plus fort, et ce genre d'astuce est légal car les individus qui ont écrit sur chaque thread ont une visibilité ordonnée, donc soyez prudent en essayant de traduire des exemples à partir de là.

1

Eh bien, je ne le pense pas.

La première instruction if:

if (counter == HeartBeatThread.counter) 
    return; 

n'a pas accès à un champ volatile et n'est pas synchronisé. Vous pouvez donc lire des données périmées pour toujours et ne jamais accéder au champ volatile. Citant l'un des commentaires de la deuxième entrée de blog: "Tout ce qui était visible pour le thread A lorsqu'il écrit dans le champ volatile f devient visible pour le thread B lorsqu'il lit f." Mais dans votre cas B (le client) ne lit jamais f (= cacheFlush). Ainsi, les modifications apportées à HeartBeatThread.counter ne doivent pas être visibles pour le client.