关于java:Volatile Vs Atomic

Volatile Vs Atomic

本问题已经有最佳答案,请猛点这里访问。

我在下面的某个地方看书。

Java volatile keyword doesn't means atomic, its common misconception
that after declaring volatile, ++ operation will be atomic, to make
the operation atomic you still need to ensure exclusive access using
synchronized method or block in Java.

那么,如果两个线程同时攻击一个volatile原语变量,会发生什么呢?

这是否意味着任何人锁定它,将首先设置它的值。如果同时,当第一个线程改变它的值时,另一个线程出现并读取旧值,那么新线程不会读取它的旧值吗?

原子关键字和易失性关键字有什么区别?


volatile关键字的作用大致是每个单独的读或写操作对该变量都是原子的。

然而,值得注意的是,一个需要多个读/写的操作(如i++,相当于一个读一个写的i = i + 1)不是原子操作,因为另一个线程可能在读和写之间写入i

Atomic类,如AtomicIntegerAtomicReference在原子上提供了更广泛的操作,特别是包括AtomicInteger的增量。


挥发性和原子性是两个不同的概念。volatile确保特定的预期(内存)状态在不同的线程之间是真的,而atomics确保对变量的操作是原子执行的。

以Java中的两个线程为例:

线程A:

1
2
value = 1;
done = true;

线程B:

1
2
if (done)
  System.out.println(value);

value = 0done = false开始,线程规则告诉我们,线程B是否打印值还不确定。此外,该点的值也未定义!为了解释这一点,您需要了解Java内存管理(这可能是复杂的),简而言之:线程可以创建变量的本地副本,JVM可以重新排序代码以优化它,因此不能保证上述代码完全按顺序运行。将done设置为true,然后将value设置为1可能是JIT优化的结果。

volatile只确保在访问此类变量时,新值将立即对所有其他线程可见,并且执行顺序确保代码处于您期望的状态。因此,在上述代码的情况下,将done定义为volatile将确保每当线程B检查变量时,它要么是假的,要么是真的;如果是真的,那么value也被设置为1。

作为volatile的副作用,这样一个变量的值被原子地设置为线程范围(执行速度的代价非常小)。然而,这只在32位系统上很重要,即使用长(64位)变量(或类似变量),在大多数其他情况下,设置/读取变量无论如何都是原子的。但是原子访问和原子操作之间有一个重要的区别。volatile只确保访问是原子性的,而atomics确保操作是原子性的。

举个例子:

1
i = i + 1;

无论如何定义i,在执行上述行时读取值的不同线程可能会得到i或i+1,因为该操作不是原子性的。如果另一个线程将i设置为不同的值,在最坏的情况下,线程a可以将我设置回以前的值,因为它只是在根据旧值计算i+1的中间,然后再次将i设置为旧值+1。说明:

1
2
3
4
Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1

像atomicinteger这样的原子确保这样的操作是原子性的。所以上面的问题不会发生,一旦两个线程都完成了,我将是1000或1001。


在多线程环境中有两个重要的概念。

  • 原子性
  • 能见度
  • volatile消除了可见性问题,但不处理原子性问题。volatile将阻止编译器对指令重新排序,该指令涉及对易失变量的写入和随后的读取。如k++。这里,k++不是一条机器指令,而是三条机器指令。

  • 复制要注册的值
  • 增量它
  • 把它放回原处
  • 因此,即使您向volatile声明变量,它也不会使此操作成为原子操作,这意味着另一个线程可以看到中间结果,该结果对于另一个线程来说是过时的或不需要的值。

    但是,AtomicIntegerAtomicReference是基于比较和交换指令的。cas有三个操作数:一个存储位置V用于操作,预期的旧值A和新值BCAS原子地将V更新为新值B,但前提是V中的值与预期的旧值A相匹配;否则它不起作用。在这两种情况下,它都返回当前在V中的值。这是由AtomicIntegerAtomicReference中的jvm使用的,它们将函数称为compareAndSet()。如果底层处理器不支持这个功能,那么JVM通过自旋锁来实现它。


    正如所指出的,volatile只处理可见性。

    在并发环境中考虑此代码段:

    1
    2
    3
    4
    5
    6
    7
    boolean isStopped = false;
        :
        :

        while (!isStopped) {
            // do some kind of work
        }

    这里的想法是,一些线程可以将EDOCX1的值(28)从false更改为true,以便向后续循环指示是时候停止循环了。

    直观地说,没有问题。逻辑上,如果另一个线程使isStopped等于true,则循环必须终止。事实上,即使另一个线程使isStopped等于true,循环也可能永远不会终止。

    这样做的原因并不直观,但要考虑到现代处理器有多个内核,并且每个内核都有多个寄存器和多个缓存级别,其他处理器无法访问这些寄存器和缓存级别。换句话说,缓存在一个处理器本地内存中的值对在不同处理器上执行的线程不可见。这就是并发性的核心问题之一:可见性。

    Java内存模型不能保证对线程中变量的更改可能对其他线程可见。为了确保更新一经创建就可以访问,必须进行同步。

    volatile关键字是一种弱同步形式。虽然它对互斥或原子性没有任何作用,但它确实提供了一种保证,即对一个线程中的某个变量所做的更改一经发生,就对其他线程可见。由于对非8字节的变量的单独读取和写入在Java中是原子的,因此声明变量EDCOX1〔0〕提供了一种便于在没有其他原子性或互斥要求的情况下提供可见性的机制。


    使用volatile关键字:

    • 使非原子64位操作具有原子性:longdouble。(所有其他的原始访问都保证是原子的!)
    • 为了确保变量更新被其他线程看到+可见性效果:在写入可变变量之后,在写入该变量之前可见的所有变量在读取相同可变变量之后(在排序之前发生)对其他线程可见。

    根据Java文档,EDCOX1的3个类是:

    A small toolkit of classes that support lock-free thread-safe
    programming on single variables. In essence, the classes in this
    package extend the notion of volatile values, fields, and array
    elements to those that also provide an atomic conditional update
    operation of the form:

    boolean compareAndSet(expectedValue, updateValue);

    原子类是围绕原子compareAndSet(...)函数构建的,该函数映射到原子CPU指令。原子类引入排序前发生,就像volatile变量那样。(有一个例外:weakCompareAndSet(...))。

    来自Java文档:

    When a thread sees an update to an atomic variable caused by a
    weakCompareAndSet, it does not necessarily see updates to any other
    variables that occurred before the weakCompareAndSet.

    关于你的问题:

    Does this mean that whosoever takes lock on it, that will be setting
    its value first. And in if meantime, some other thread comes up and
    read old value while first thread was changing its value, then doesn't
    new thread will read its old value?

    您不锁定任何东西,您描述的是一个典型的竞争条件,如果线程在没有适当同步的情况下访问共享数据,最终会发生这种情况。如前所述,在这种情况下声明变量volatile只会确保其他线程看到变量的更改(该值不会缓存在只有一个线程看到的某些缓存的寄存器中)。

    What is the difference between AtomicInteger and volatile int?

    AtomicIntegerint上提供原子操作,并进行适当的同步(如incrementAndGet()getAndAdd(...)…),volatile int只会确保int对其他线程的可见性。


    So what will happen if two threads attack a volatile primitive variable at same time?

    通常每个都可以增加值。但是,有时两个线程都会同时更新值,而不是增加2个总数,而是同时增加1个线程和1个线程。

    Does this mean that whosoever takes lock on it, that will be setting its value first.

    没有锁。这就是synchronized的目的。

    And in if meantime, some other thread comes up and read old value while first thread was changing its value, then doesn't new thread will read its old value?

    对,

    What is the difference between Atomic and volatile keyword?

    atomicXXXX包装了一个volatile,所以它们基本相同,区别在于它提供了更高级别的操作,例如用于实现增量的compareAndSwap。

    AtomicXXXX还支持Lazyset。这就像一个易失性集,但不会延迟管道等待写入完成。这可能意味着,如果你读一个值,你只需写,你可能会看到旧的值,但无论如何你不应该这样做。不同的是,设置一个易失性大约需要5纳秒,而位Lazyset大约需要0.5纳秒。