java - sur - plan de bombe atomique




Quelle est la différence entre atomique/volatile/synchronisé? (4)

Je sais que deux threads ne peuvent pas entrer dans le bloc Synchroniser en même temps

Deux threads ne peuvent pas entrer deux fois un bloc synchronisé sur le même objet. Cela signifie que deux threads peuvent entrer dans le même bloc sur différents objets. Cette confusion peut conduire à un code comme celui-ci.

private Integer i = 0;

synchronized(i) {
   i++;
}

Cela ne se comportera pas comme prévu car il pourrait être verrouillé sur un objet différent à chaque fois.

si cela est vrai que Comment this atomic.incrementAndGet () fonctionne sans Synchronize ?? et est thread safe?

Oui. Il n'utilise pas de verrouillage pour assurer la sécurité du filetage.

Si vous voulez savoir comment ils fonctionnent plus en détail, vous pouvez lire le code pour eux.

Et quelle est la différence entre la lecture interne et l'écriture de Variable Volatile / Variable Atomique?

La classe Atomic utilise des champs volatils . Il n'y a pas de différence sur le terrain. La différence est les opérations effectuées. Les classes Atomic utilisent les opérations CompareAndSwap ou CAS.

J'ai lu dans un article que le fil a une copie locale des variables qu'est-ce que c'est ??

Je ne peux que supposer que cela fait référence au fait que chaque CPU a sa propre vue en mémoire cache qui peut être différente de toutes les autres CPU. Pour garantir que votre UC dispose d'une vue cohérente des données, vous devez utiliser des techniques de sécurité des threads.

C'est seulement un problème quand la mémoire est partagée qu'au moins un thread le met à jour.

Comment fonctionne atomique / volatile / synchronisé en interne?

Quelle est la différence entre les blocs de code suivants?

Code 1

private int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Code 2

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

Code 3

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Le volatile fonctionne-t-il de la manière suivante? Est

volatile int i = 0;
void incIBy5() {
    i += 5;
}

équivalent à

Integer i = 5;
void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

Je pense que deux threads ne peuvent pas entrer dans un bloc synchronisé en même temps ... ai-je raison? Si cela est vrai alors comment atomic.incrementAndGet() fonctionne sans synchronized ? Et est-ce thread-safe?

Et quelle est la différence entre la lecture interne et l'écriture de variables volatiles / variables atomiques? J'ai lu dans un article que le fil a une copie locale des variables - qu'est-ce que c'est?


Déclarer une variable comme volatile signifie que la modification de sa valeur affecte immédiatement le stockage de la mémoire réelle pour la variable. Le compilateur ne peut pas optimiser les références faites à la variable. Cela garantit que lorsqu'un thread modifie la variable, tous les autres threads voient immédiatement la nouvelle valeur. (Ceci n'est pas garanti pour les variables non volatiles.)

La déclaration d'une variable atomique garantit que les opérations effectuées sur la variable se produisent de manière atomique, c'est-à-dire que toutes les sous-étapes de l'opération sont terminées dans le thread elles sont exécutées et ne sont pas interrompues par d'autres threads. Par exemple, une opération d'incrémentation et de test nécessite que la variable soit incrémentée et ensuite comparée à une autre valeur; une opération atomique garantit que ces deux étapes seront complétées comme s'il s'agissait d'une seule opération indivisible / ininterrompue.

La synchronisation de tous les accès à une variable n'autorise qu'un seul thread à la fois à accéder à la variable et oblige tous les autres threads à attendre que l'accès au thread libère son accès à la variable.

L'accès synchronisé est similaire à l'accès atomique, mais les opérations atomiques sont généralement implémentées à un niveau de programmation inférieur. En outre, il est tout à fait possible de synchroniser seulement certains accès à une variable et de permettre que d'autres accès soient désynchronisés (par exemple, synchroniser toutes les écritures vers une variable mais aucune des lectures de celle-ci).

L'atomicité, la synchronisation et la volatilité sont des attributs indépendants, mais sont généralement utilisés conjointement pour renforcer la coopération des threads afin d'accéder aux variables.

Addendum (avril 2016)

L'accès synchronisé à une variable est généralement implémenté à l'aide d'un moniteur ou d'un sémaphore . Ce sont des mécanismes de mutex de bas niveau (exclusion mutuelle) qui permettent à un thread d'acquérir le contrôle exclusif d'une variable ou d'un bloc de code, forçant tous les autres threads à attendre s'ils essaient également d'acquérir le même mutex. Une fois que le thread propriétaire libère le mutex, un autre thread peut acquérir le mutex à son tour.

Addendum (juillet 2016)

La synchronisation se produit sur un objet . Cela signifie qu'appeler une méthode synchronisée d'une classe verrouillera l'objet this de l'appel. Les méthodes synchronisées statiques verrouillent l'objet Class lui-même.

De même, entrer un bloc synchronisé nécessite de verrouiller l'objet de la méthode.

Cela signifie qu'une méthode synchronisée (ou un bloc) peut être exécutée dans plusieurs threads en même temps s'ils se verrouillent sur des objets différents , mais qu'un seul thread peut exécuter une méthode synchronisée (ou un bloc) à la fois pour un seul objet donné.


Une synchronisation volatile + est une solution infaillible pour une opération (instruction) totalement atomique qui inclut plusieurs instructions à la CPU.

Dites par exemple: volatile int i = 2; i ++, qui n'est rien d'autre que i = i + 1; ce qui fait de moi la valeur 3 dans la mémoire après l'exécution de cette instruction. Cela inclut la lecture de la valeur existante de la mémoire pour i (qui est 2), charger dans le registre de l'UC et faire avec le calcul par incrément la valeur existante avec un (2 + 1 = 3 dans l'accumulateur) puis réécrire cette valeur incrémentée Retour à la mémoire. Ces opérations ne sont pas assez atomiques bien que la valeur de i soit volatile. Je suis volatile garantit seulement qu'une seule lecture / écriture de la mémoire est atomique et pas avec MULTIPLE. Par conséquent, nous avons besoin d'avoir synchronisé aussi autour de i ++ pour le garder comme une preuve atomique. Rappelez-vous le fait qu'une déclaration comprend plusieurs déclarations.

J'espère que l'explication est assez claire.


Vous demandez spécifiquement comment ils travaillent en interne , alors voici:

Pas de synchronisation

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Il lit essentiellement la valeur de la mémoire, l'incrémente et la remet en mémoire. Cela fonctionne dans un thread unique mais de nos jours, à l'ère des caches multi-cœurs, multi-CPU, multi-niveaux, cela ne fonctionnera pas correctement. Tout d'abord, il introduit les conditions de concurrence (plusieurs threads peuvent lire la valeur en même temps), mais aussi des problèmes de visibilité. La valeur peut seulement être stockée dans la mémoire de l'unité centrale " locale " (un peu de cache) et ne pas être visible pour les autres processeurs / cœurs (et donc - les threads). C'est pourquoi beaucoup se réfèrent à la copie locale d'une variable dans un thread. C'est très dangereux. Considérez ce code thread-stop populaire mais brisé:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

public void pleaseStop() {
    stopped = true;
}

Ajouter volatile à la variable stopped et ça marche bien - si n'importe quel autre thread modifie la variable pleaseStop() par la méthode pleaseStop() , vous êtes assuré de voir cette modification immédiatement dans la boucle while(!stopped) pleaseStop() du thread de travail. BTW ce n'est pas un bon moyen d'interrompre un thread non plus, voir: Comment arrêter un thread qui est en cours d'exécution pour toujours sans utilisation et l' arrêt d'un thread java spécifique .

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

La classe AtomicInteger utilise les opérations CPU de bas niveau CAS ( compare-and-swap ) (pas de synchronisation nécessaire!) Elles vous permettent de modifier une variable particulière uniquement si la valeur actuelle est égale à autre chose (et est retournée avec succès). Ainsi, lorsque vous exécutez getAndIncrement() il s'exécute en boucle (implémentation réelle simplifiée):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

Donc, fondamentalement: lire; essayez de stocker la valeur incrémentée; Si elle n'est pas réussie (la valeur n'est plus égale au current ), lisez et réessayez. Le compareAndSet() est implémenté en code natif (assembly).

volatile sans synchronisation

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Ce code n'est pas correct. Il corrige le problème de visibilité ( volatile s'assure que les autres threads peuvent voir les modifications apportées au counter ) mais a toujours une condition de concurrence. Cela a été explained plusieurs fois: la pré / post-incrémentation n'est pas atomique.

Le seul effet secondaire de volatile est de " vider " les caches afin que toutes les autres parties voient la version la plus fraîche des données. C'est trop strict dans la plupart des situations; c'est pourquoi volatile n'est pas par défaut.

volatile sans synchronisation (2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

Le même problème que ci-dessus, mais pire encore parce que i ne i pas private . La condition de course est toujours présente. Pourquoi est-ce un problème? Si, disons, deux threads exécutent ce code simultanément, la sortie peut être + 5 ou + 10 . Cependant, vous êtes assuré de voir le changement.

Multiple indépendant synchronized

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

Surprise, ce code est également incorrect. En fait, c'est complètement faux. Tout d'abord, vous vous synchronisez sur i , qui est sur le point d'être changé (de plus, i un primitif, donc je suppose que vous synchronisez sur un Integer temporaire créé via autoboxing ...) Complètement vicié. Vous pourriez aussi écrire:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

Deux threads ne peuvent pas entrer dans le même bloc synchronized avec le même verrou . Dans ce cas (et de même dans votre code), l'objet de verrouillage change à chaque exécution, de sorte que la synchronized n'a aucun effet.

Même si vous avez utilisé une variable finale (ou this ) pour la synchronisation, le code est toujours incorrect. Deux threads peuvent d'abord lire i à temp synchrone (ayant la même valeur localement en temp ), puis le premier affecte une nouvelle valeur à i (disons de 1 à 6) et l'autre fait la même chose (de 1 à 6) .

La synchronisation doit s'étendre de la lecture à l'attribution d'une valeur. Votre première synchronisation n'a aucun effet (lire un int est atomique) et la seconde aussi. À mon avis, ce sont les formes correctes:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}




volatile