Java多线程i++线程安全问题,volatile和AtomicInteger解释?
巴扎黑
巴扎黑 2017-04-18 09:53:42
0
5
875

在Java多线程中,i++和i--是非线程安全的。
例子:

public class PlusPlusTest {

    public static void main(String[] args) throws InterruptedException {
        Num num = new Num();
        ThreadA threadA = new ThreadA(num);
        ThreadB threadB = new ThreadB(num);
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        System.out.println(num.count);
    }
}

class ThreadA extends Thread {
    private Num num;

    public ThreadA(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num.count++;
        }
    }
}

class ThreadB extends Thread {
    private Num num;

    public ThreadB(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num.count++;
        }
    }
}

class Num {
    int count = 0;

    public Num() {
    }
}

以上代码输出结果基本上不是2000,会比2000小。

原因:

在线程A中,i++的过程为:
temp1 = i; temp2 = temp1 + 1; i = temp2;
在线程B中,i++的过程为:
temp3 = i; temp4 = temp3 + 1; i = temp4;

在i=0的时候,线程A和B同时读取i=0。
线程A执行++后,i被修改成1。
线程B执行++后,i被修改,但还是1。

问:这样的解释对么?


想到把count变量申明为volatile,但是:

即使把count申明为volatile,输出的结果也不是2000,请问为什么?

class Num {
    volatile int count = 0;

    public Num() {
    }
}


最后

把count变量包装成AtomicInteger之后,输出的结果为2000,正确,这又是为什么?

巴扎黑
巴扎黑

répondre à tous(5)
伊谢尔伦

Parce que volatile ne garantit pas l'atomicité de l'opération, i++ cette opération n'est pas une opération atomique.

大家讲道理

Q : Cette explication est-elle correcte ?

n'est en fait pas très approprié. Le fonctionnement de i++ ne devrait pas être si gênant. La valeur de lecture de fait référence à la lecture du CPU .
Une erreur s'est produite, la séquence d'exécution est la suivante :

  1. 线程1 lit que la valeur de i est 0,

  2. 线程2 indique également que la valeur de i est 0,

  3. 线程1 a effectué l'opération +1 et a écrit la valeur du résultat 1 dans la mémoire,

  4. 线程2 effectue l'opération +1 et écrit la valeur du résultat 1 dans la mémoire.

Même si le compte est déclaré volatile, le résultat de sortie n'est pas 2000. Pourquoi ?

volatile ne peut garantir que la visibilité, ce qui signifie que la dernière valeur de i peut être lue en temps réel, mais il ne peut pas garantir l'atomicité, c'est-à-dire que la séquence d'exécution ci-dessus est complètement autorisée à se produire. Vous pouvez également vous référer à ma réponse à cette question : https://segmentfault.com/q/10...

Après avoir enveloppé la variable count dans AtomicInteger, le résultat de sortie est 2000, ce qui est correct. Pourquoi ?

AtomicInteger est un int atomique. Ceci est implémenté par Java Comprenons grossièrement le code source :

.
 /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

Le point clé est compareAndSet(). Cette méthode déterminera si les valeurs de current et i remplissent les conditions : 此时i的值是否和current相等 Si les conditions sont remplies, quittez directement la boucle. , 再++ sera répété jusqu'à ce qu'il soit normal.
compareAndSet方法 est implémenté par Java的unsafe Cela devrait être de très bas niveau. Ce sont toutes des méthodes native, et je ne les ai pas étudiées. Cependant, le programmeur moyen ne sera pas exposé à la unsafe programmation.

Peter_Zhu

Volatile ne peut garantir que la visibilité, c'est-à-dire que vous pouvez le lire immédiatement après que quelqu'un d'autre l'a modifié, mais d'autres peuvent également le modifier lorsque vous le modifiez.
AtomicInteger est basé sur CAS (Compare And Swap).

CAS a 3 opérandes, une valeur mémoire V, une ancienne valeur attendue A et une nouvelle valeur à modifier B. Si et seulement si la valeur attendue A et la valeur mémoire V sont identiques, modifiez la valeur mémoire V en B, sinon ne faites rien. Deux problèmes : (1) L'algorithme CAS peut toujours entrer en conflit. Par exemple, entre deux threads A et B, A est entré dans la mémoire d'écriture mais ne l'a pas terminé à ce moment-là, A lit la copie et la lecture est réussie, et les deux threads AB simultanément L'entrée dans l'opération d'écriture en mémoire provoquera inévitablement des conflits. L'essence de l'algorithme CAS n'est pas complètement sans verrouillage, mais retarde l'acquisition et la libération des verrous jusqu'à ce que la primitive CPU soit implémentée, ce qui équivaut à réduire autant que possible la portée du verrou, il réalise directement le changement du ; état du système par exclusion mutuelle. L'idée de base de son utilisation est la copie sur écriture - Après avoir modifié la copie de l'objet, utilisez l'opération CAS pour remplacer la copie par l'original. (2) Problème ABA. Si l'un des threads modifie A->B->A, l'autre thread lit toujours A. Bien que la valeur soit la valeur attendue, cela ne signifie pas que la valeur de la mémoire n'a pas changé.

巴扎黑

Je suis trop paresseux pour écrire une réponse Voici un très bon article : Programmation simultanée en Java : analyse des mots clés volatiles

小葫芦

Volatile garantit que les données obtenues à chaque fois sont les plus récentes (lues depuis la mémoire), i++; --> i=i+1; Si i+1 est exécuté sans attribuer de valeur à i, il n'y a aucune garantie qu'une autre Les données obtenues par le thread sont les dernières, et cette dernière est une opération atomique, on peut donc garantir que i = this sera définitivement exécuté

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal