【问题标题】:Java Volatile variable still causing race condition [duplicate]Java Volatile变量仍然导致竞争条件[重复]
【发布时间】:2021-09-05 09:05:20
【问题描述】:

我正在对处理多线程 Java 应用程序中的竞争条件的不同方法进行实验。像原子变量这样的策略,同步效果很好,但我没有看到使用 volatile 变量时问题得到解决。这里的代码和输出供参考。

您能否指导一下 volatile 变量仍会导致竞态条件的原因是什么?

package com.shyam.concurrency;


public class main {
    public static void main(String[] args) {

   demoClass dm1 = new demoClass();
        Thread th1 = new Thread(()->{
        int i =0;
        do {
            i++;
            dm1.setCounter();
            dm1.setAtomicCounter();
            dm1.setSyncCounter();
            dm1.setVolatileCounter();
        } while (i < 100000);
        });

        Thread th2 = new Thread(()->{
            int i =0;
            do {
                i++;
                dm1.setCounter();
                dm1.setAtomicCounter();
                dm1.setSyncCounter();
                dm1.setVolatileCounter();
            } while (i < 100000);

        });

        th1.start();
        th2.start();

        try {
            th1.join();
            th2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Normal counter(Race condition) : " + dm1.getCounter() );
        System.out.println("Synchronized counter is :" + dm1.getSyncCounter());
        System.out.println("Atomic counter is :" + dm1.getAtomicCounter());
        System.out.println("Volatile counter is :" + dm1.getVolatileCounter());

有增量逻辑的代码在这里:

package com.shyam.concurrency;

import java.util.concurrent.atomic.AtomicInteger;

public class demoClass {
    private  int counter ;
    private int syncCounter;
    private volatile  int volatileCounter = 0;
    private AtomicInteger  atomicCounter  = new AtomicInteger() ;


 
    public int getAtomicCounter() {
        return atomicCounter.intValue();
    }

    public void setAtomicCounter() {
        this.atomicCounter.addAndGet(1);
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter() {
        this.counter++;
    }
    public synchronized int getSyncCounter() {
        return syncCounter;
    }

    public synchronized void setSyncCounter() {
        this.syncCounter++;
    }

    public int getVolatileCounter() {
        return volatileCounter;
    }

    public void setVolatileCounter() {
        this.volatileCounter++;
    }
}

这是我得到的输出:

Normal counter(Race condition) : 197971
Synchronized counter is :200000
Atomic counter is :200000
Volatile counter is :199601

【问题讨论】:

  • 好吧,我还没有阅读你的代码,但问题可能是原子性。 volatile 不提供,而您提到的其他两种方法提供。因此,只需为您的问题选择正确的方法即可。
  • 现在我已经阅读了您的代码,问题肯定是原子性。 volatile 众所周知不会帮助进行这种访问。它不起作用,因为规范说它不会。
  • volatile 变量的读/修改/写操作不是原子操作。两个线程可以交错,一个正在读取,而另一个正在递增或其他方式。如果需要原子性,就不能选择只保证可见性的操作。

标签: java multithreading synchronized volatile atomicinteger


【解决方案1】:

可见性与原子性

volatile 只解决了可见性的问题。这意味着每个线程都会看到变量的当前值,而不是可能看到过时的缓存值。

您的线路:

this.volatileCounter++;

…正在执行多个操作:

  • 获取该变量的当前值
  • 增加该值
  • 将新值存储在该变量中

这组操作不是原子的

当一个线程获取了值但尚未递增并存储新值时,第二个线程可以访问相同的当前值。两个线程递增相同的初始值,因此两个线程都生成并保存相同的冗余新值。

例如,两个或更多线程可能会访问值 42。然后所有这些线程将递增到 43,并且每个线程将存储 43。该数字 43 将被一遍又一遍地存储。其他一些线程甚至可能已经看到了这 43 次写入中的一个,然后递增并存储了 44。尚未写入 43 的剩余线程之一最终会覆盖 44 的写入。因此,您不仅可能会浪费一些尝试来增加因此无法将数字向前移动,您实际上可能会看到数字向后移动(实际上是递减)。

如果你想使用volatile,你必须保护代码以使多个操作原子化。 synchronized 关键字就是这样一种解决方案。

就个人而言,我更喜欢使用AtomicInteger 方法。如果您在任何访问尝试之前实例化 AtomicInteger,并且从不替换该实例,则该 AtomicInteger 的引用变量的可见性是不成问题的。没有机会获得过时的缓存值意味着没有可见性问题。关于对其有效负载的快速访问,AtomicInteger 的方法为简单操作提供了原子性(显然,因此得名),

要了解有关可见性问题的更多信息,请参阅Java Memory Model。并阅读 Brian Goetz 等人的优秀书籍 Java Concurrency In Practice

【讨论】:

  • @SolomonSlow 我将添加一个说明。谢谢。
  • 只有读-修改-写不能通过易失性读写实现原子化。但实际加载和实际存储是原子的。
  • 这个解释并不完全正确。 Volatile 处理 3 个方面:可见性、原子性和排序;不仅能见度。如果你这样做,例如写入(发布),稍后您将对相同的 volatile 变量进行读取(获取),然后在发布之前对所有加载/存储进行排序。而对于获取,它将在获取之后对所有加载和存储进行排序。 Volatile 还保证了原子性;在这种特殊情况下,您不会遇到撕裂的读取或撕裂的写入。
猜你喜欢
  • 1970-01-01
  • 2019-03-21
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2015-06-27
  • 2012-04-29
  • 1970-01-01
相关资源
最近更新 更多