Quel est le but d’utiliser synchronisé (Thread.currentThread ()) {…} en Java?

J’ai rencontré le code suivant dans notre projet:

synchronized (Thread.currentThread()){ //some code } 

Je ne comprends pas la raison d’utiliser synchronisé sur currentThread .

Y a-t-il une différence entre

 synchronized (Thread.currentThread()){ //some code } 

et juste

 //some code 

Pouvez-vous donner un exemple qui montre la différence?

METTRE À JOUR

plus en détail ce code comme suit:

 synchronized (Thread.currentThread()) { Thread.currentThread().wait(timeInterval); } 

Cela ressemble à Thread.sleep(timeInterval) . Est-ce la vérité?

considère ceci

  Thread t = new Thread() { public void run() { // A synchronized (Thread.currentThread()) { System.out.println("A"); try { Thread.sleep(5000); } catch (InterruptedException e) { } } } }; t.start(); synchronized (t) { // B System.out.println("B"); Thread.sleep(5000); } 

les blocs A et B ne peuvent pas être exécutés simultanément, de sorte que dans le test donné, la sortie “A” ou “B” sera retardée de 5 secondes, la première étant indéfinie

Bien que ce soit presque un anti-modèle et qu’il devrait être résolu différemment, votre question immédiate appelle toujours une réponse. Si votre base de code entière n’obtient jamais un verrou sur une instance Thread autre que Thread.currentThread() , alors ce verrou ne sera jamais contesté. Cependant, si vous avez ailleurs

 synchronized (someSpecificThreadInstance) { ... } 

alors un tel bloc devra composer avec le bloc montré pour le même verrou. Il peut en effet arriver que le thread atteignant la synchronized (Thread.currentThread()) doit attendre qu’un autre thread abandonne le verrou.

Fondamentalement, il n’y a pas de différence entre la présence et l’absence du bloc synchronized . Cependant, je peux penser à une situation qui pourrait donner un autre sens à cet usage.

Les blocs synchronized ont un effet secondaire intéressant: ils créent une barrière de mémoire créée par le runtime avant d’entrer et après la sortie du bloc. Une barrière de mémoire est une instruction spéciale adressée à la CPU qui applique toutes les variables partagées entre plusieurs threads pour renvoyer leurs dernières valeurs. En général, un thread fonctionne avec sa propre copie d’une variable partagée et sa valeur est visible uniquement pour ce thread. Une barrière de mémoire indique au thread de mettre à jour la valeur de manière à ce que le changement soit visible pour les autres threads.

Donc, le bloc synchronisé dans ce cas ne fait pas de locking (car il n’y aura pas de cas réel de situation de locking et d’attente, à moins que je ne puisse penser) (sauf si le cas d’utilisation mentionné dans cette réponse est résolu) au lieu de cela, il applique les valeurs des champs partagés pour renvoyer leur dernière valeur. Ceci est cependant vrai si les autres endroits du code qui fonctionnent avec les variables en question utilisent également des barrières de mémoire (comme avoir le même bloc synchronized autour des opérations de mise à jour / réaffectation). Cependant, ce n’est pas une solution pour éviter les conditions de course.

Si cela vous intéresse, je vous recommande de lire cet article . Il s’agit de barrières mémoire et de locking dans C # et le framework .NET, mais le problème est similaire pour Java et la JVM (sauf pour le comportement des champs volatils). Cela m’a beaucoup aidé à comprendre comment fonctionnent les threads, les champs volatiles et les verrous en général.

On doit tenir compte de certaines considérations sérieuses dans cette approche, qui ont été mentionnées dans les commentaires ci-dessous.

  • La barrière de mémoire n’implique pas le locking. L’access sera toujours non synchronisé et soumis à des conditions de course et à d’autres problèmes potentiels. Le seul avantage est que le thread peut lire les dernières valeurs des champs de mémoire partagée, sans utiliser de verrou. Certaines pratiques utilisent des approches similaires si le thread de travail ne lit que des valeurs et ne prend en compte que les plus présentes, tout en évitant la surcharge des verrous – un cas d’utilisation pourrait être un algorithme de traitement de données simultané haute performance.
  • L’approche ci-dessus n’est pas fiable . Selon le commentaire de Holger , le compilateur pourrait éliminer les instructions de locking lors de l’optimisation, car il pourrait les considérer comme inutiles. Cela permettra également de supprimer les barrières de mémoire. Le code n’émettra alors pas de verrou et il ne fonctionnera pas comme prévu si un verrou était destiné à être utilisé ou si l’objective était de créer une barrière de mémoire.
  • L’approche ci-dessus est également peu fiable car la machine virtuelle Java d’exécution peut supprimer la synchronisation lorsqu’elle peut prouver que le moniteur ne sera jamais acquis par un autre thread, ce qui est vrai pour cette construction si le code ne se synchronise jamais sur un autre object thread . Donc, même si cela fonctionne pendant les tests sur le système A, cela pourrait échouer sous une autre JVM sur le système B. Pire encore, le code pourrait fonctionner pendant un certain temps et cesser de fonctionner lorsque des optimisations sont appliquées.
  • Les intentions du code, dans la mesure où il rest maintenant, sont ambiguës, il faut donc utiliser des moyens plus explicites et expressifs pour obtenir son effet (voir le commentaire de Marko Topolnik pour référence).

Vous implémentez un mutex récursif .

c’est-à-dire que le même thread peut entrer dans le bloc de synchronisation, mais pas les autres threads.