2017-04-22 1 views
4

Considérons la synchronisation standard suivante en Java:Java - comment Lock garantit-il la relation qui se passe avant?

public class Job { 
    private Lock lock = new ReentrantLock(); 

    public void work() { 
     lock.lock(); 
     try { 
      doLotsOfWork(); 
     } finally { 
      lock.unlock(); 
     } 
    } 
} 

Je comprends, basé sur Javadoc, que cela équivaut à synchronized bloc. J'ai du mal à voir comment cela est effectivement appliqué au niveau inférieur.

Lock a un état qui est volatile, lors de l'appel à lock() il fait une lecture volatile, puis lors de la libération, il effectue une écriture volatile. Comment une écriture dans un état d'un objet peut-elle garantir que aucune des instructions de doLotsOfWork, qui pourrait toucher beaucoup d'objets différents, ne sera exécutée dans le désordre?

Ou imaginez que doLotsOfWork est effectivement remplacé par 1000+ lignes de code. Il est clair que le compilateur ne peut pas savoir à l'avance qu'il y a un volatile quelque part à l'intérieur de la serrure, donc il doit arrêter de réorganiser les instructions. Alors, comment cela se passe - avant garanti pour lock/unlock, même si elle est construite autour de l'état volatile d'un objet séparé?

+0

Je ne vois aucune volatilité dans l'implémentation Lock/ReentrantLock. Vous pouvez vérifier l'implémentation de ReentraltLock à l'adresse suivante: http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/locks/ReentrantLock.java/?v= source –

+0

Via son implémentation. Ce n'est pas défini. Seulement requis. – EJP

+0

L'état de AbstractQueuedSYnchronizer est une variable volatile – Bober02

Répondre

2

Eh bien, si j'ai bien compris, votre réponse est here.volatile écrit et lit introduire barrières de mémoire: LoadLoad, LoadStore, etc. que interdire re-commander. Au niveau de la CPU, cela se traduit par des barrières de mémoire telles que mfence ou lfence (la CPU force également le non-réordonnancement via d'autres mécanismes, de sorte que vous pouvez voir autre chose dans le code machine).

Voici un petit exemple:

i = 42; 
j = 53; 
[StoreStore] 
[LoadStore] 
x = 1; // volatile store 

i et j affectations peuvent être commandés entre alors, mais ils ne peuvent pas avec x=1 ou en d'autres termes i and j ne peuvent pas aller au-dessous x.

La même chose s'applique au volatile reads.

Pour votre exemple, toutes les opérations à l'intérieur doLotsOfWork peuvent être réorganisées à la demande du compilateur, mais elles ne peuvent pas être réordonnées avec lock operations.

Également lorsque vous dites que le compilateur ne peut pas savoir qu'il existe un volatile read/write, vous avez un peu tort. Il doit savoir que, sinon il n'y aurait pas d'autre moyen d'empêcher ces réordonnances.

En outre, dernière note: depuis jdk-8, vous pouvez appliquer des non-réordonnances via le Unsafe qui fournit des moyens à cela en plus volatile.

1

ceux d'Oracle documentation:

Une écriture à un champ volatilearrive-avant chaque lecture ultérieure de ce même domaine. Les écritures et les lectures des champs volatile ont des effets de cohérence de la mémoire similaires à en entrée et en sortie des moniteurs, mais et non impliquent un verrouillage mutuel par exclusion.

Java dans la pratique Concurrency affirme encore plus clairement:

Les effets de visibilité de volatile les variables vont au-delà de la valeur de la variable volatile elle-même. Quand un thread Un écrit à un volatile variable et ensuite fil B lit que même variable, les valeurs de toutes variables qui étaient visibles à A avant d'écrire à la variable volatile deviennent visibles à B après avoir lu la variable volatile .

Appliqué à ReentrantLock cela signifie que tout exécuté avant lock.unlock() (doLotsOfWork() dans votre cas) sera garanti à se produire avant appel ultérieur à lock.lock(). Les instructions à l'intérieur doLotsOfWork() peuvent encore être réorganisées entre eux. La seule chose qui est garantie ici est que tout thread qui va ensuite acquérir le verrou appelant lock.lock() verra toutes les modifications effectuées dans doLotsOfWork() avant d'appeler lock.unlock().

+0

Je comprends cela. Ceci est une description de haut niveau de JMM. Je veux savoir comment une écriture dans une variable volatile implique qu'il n'y a pas de réordonnance qui pourrait l'endommager, ce que le compilateur ne peut pas savoir à l'avance (qu'il y ait une variable d'état qui est volatile) – Bober02