首页 文章

易挥发的Vs原子[重复]

提问于
浏览
83

这个问题在这里已有答案:

我读到了下面的某个地方 .

Java volatile关键字并不意味着原子,它常见的误解是,在声明volatile之后,操作将是原子的,要使操作原子化,你仍然需要确保使用Java中的synchronized方法或块进行独占访问 .

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

这是否意味着无论谁锁定它,都将首先设定其 Value . 如果在此期间,当第一个线程更改其值时,某些其他线程出现并读取旧值,那么新线程是否会读取其旧值?

Atomic和volatile关键字有什么区别?

6 回答

  • 20

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

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

    Atomic 类,如 AtomicIntegerAtomicReference ,以原子方式提供更多种类的操作,特别是包括 AtomicInteger 的增量 .

  • 40

    挥发性和原子性是两个不同的概念 . Volatile确保在不同的线程中某个预期(内存)状态为真,而Atomics确保对变量的操作以原子方式执行 .

    以Java中的两个线程为例:

    线程A:

    value = 1;
    done = true;
    

    线程B:

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

    value = 0done = false 开始,线程规则告诉我们,线程B是否打印值是未定义的 . Furthermore value is undefined at that point as well! 为了解释这一点,您需要了解一下Java内存管理(可能很复杂),简而言之:线程可能会创建变量的本地副本,而JVM可以对代码进行重新排序以优化它,因此无法保证以上内容代码以该顺序运行 . 设置为true和 then 设置值为1可能是JIT优化的可能结果 .

    volatile 只能确保在访问此类变量时,新值将立即对所有其他线程可见 and 执行顺序确保代码处于您期望的状态 . 因此,在上面的代码中,将 done 定义为volatile将确保每当线程B检查变量时,它都是false或true,如果为true,则 value 也被设置为1 .

    作为volatile的副作用,这种变量的值在线程范围内以原子方式设置(执行速度非常小) . 然而,这对于i.E.的32位系统来说非常重要 . 使用长(64位)变量(或类似),在大多数其他情况下,设置/读取变量无论如何都是原子的 . 但原子访问和原子操作之间存在重要区别 . Volatile仅确保访问是原子的,而Atomics确保操作是原子的 .

    请看以下示例:

    i = i + 1;
    

    无论你如何定义i,在执行上述行时读取值的不同Thread可能得到i或i 1,因为操作不是原子的 . 如果另一个线程将i设置为不同的值,在最坏的情况下,我可以将其设置回线程A之前的任何值,因为它只是在基于旧值计算i 1的中间,然后再次设置i那个旧的 Value 1.解释:

    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 .

  • 56

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

    • 原子性

    • 能见度

    Volatile 消除了可见性问题,但它没有处理原子性问题 . Volatile 将阻止编译器重新排序涉及写入和随后读取volatile变量的指令 . 例如 k++ 这里 k++ 不是一台机器指令,而是三台机器指令 .

    • 将值复制到寄存器

    • 增加它

    • 把它放回去

    因此,即使您将变量声明为 volatile ,它也不会使此操作成为原子,这意味着另一个线程可以看到中间结果,这是一个陈旧或不需要的值,其他线程 .

    AtomicIntegerAtomicReference 基于Compare and swap instruction . CAS有三个操作数:要操作的内存位置 V ,预期的旧值 A 和新值 B . CAS 原子地将 V 更新为新值 B ,但仅当 V 中的值与预期的旧值 A 匹配时才有效;否则它什么都不做 . 在任何一种情况下,它都会返回当前 V 中的值 . JVM在 AtomicIntegerAtomicReference 中使用它,它们将函数称为 compareAndSet() . 如果底层处理器不支持此功能,则JVM通过spin lock实现它 .

  • 11

    正如所示, volatile 仅处理可见性 .

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

    boolean isStopped = false;
        :
        :
    
        while (!isStopped) {
            // do some kind of work
        }
    

    这里的想法是,某些线程可以将 isStopped 的值从false更改为true,以便向后续循环指示是时候停止循环 .

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

    其原因并不直观,但考虑到现代处理器具有多个内核,并且每个内核都有多个寄存器和多级缓存内存 are not accessible to other processors . 换句话说,在一个处理器的本地内存中缓存的值对于在不同处理器上执行的线程是 not visisble . 这是并发的核心问题之一:可见性 .

    Java内存模型无法保证何时对线程中的变量所做的更改可能对其他线程可见 . 为了保证更新在发布后立即可见,您必须进行同步 .

    volatile 关键字是一种弱同步形式 . 虽然它对互斥或原子性没有任何作用,但它确实提供了保证,一个线程中对变量的更改一旦生成就会对其他线程可见 . 因为对Java中非8字节变量的单独读取和写入是原子的,所以声明变量 volatile 提供了一种简单的机制,可以在没有其他原子性或互斥要求的情况下提供可见性 .

  • 11

    那么如果两个线程同时攻击一个易变的原始变量会发生什么呢?

    通常每个人都可以增加值 . 但是有时候,两者都会同时更新值而不是递增2,而是增加1和1的线程增量 .

    这是否意味着无论谁锁定它,都将首先设置其值 .

    没有锁 . 这就是 synchronized 的用途 .

    并且如果在此期间,当第一个线程正在更改其值时,某些其他线程会出现并读取旧值,那么新线程是否会读取其旧值?

    是,

    Atomic和volatile关键字有什么区别?

    AtomicXxxx包含一个volatile,因此它们基本相同,不同之处在于它提供了更高级别的操作,例如用于实现增量的CompareAndSwap .

    AtomicXxxx还支持lazySet . 这就像一个易失性集,但不会使管道停止等待写完成 . 这可能意味着如果你读到一个你刚写的值,你可能会看到旧值,但你不应该这样做 . 不同之处在于设置volatile需要大约5 ns,bit lazySet大约需要0.5 ns .

  • 108

    使用 volatile 关键字:

    • 使非原子64位操作成为原子: longdouble . (所有其他的,原始访问已经保证是原子的!)

    • 使变量更新保证被其他线程可见性效果看到:在写入volatile变量之后,在读取相同的volatile变量(在排序之前发生)之后,在写入该变量之前可见的所有变量都变得对另一个线程可见 .

    根据java docsjava.util.concurrent.atomic.* 类是:

    一个小型工具包,支持对单个变量进行无锁线程安全编程 . 实质上,此包中的类将volatile值,字段和数组元素的概念扩展为也提供表单的原子条件更新操作的概念:boolean compareAndSet(expectedValue,updateValue);

    原子类是围绕映射的原子 compareAndSet(...) 函数构建的到原子CPU指令 . 原子类引入了 volatile 变量之前发生的事件 . (有一个例外: weakCompareAndSet(...) ) .

    来自java文档:

    当线程看到由weakCompareAndSet引起的原子变量更新时,它不一定会看到对weakCompareAndSet之前发生的任何其他变量的更新 .

    对于你的问题:

    这是否意味着无论谁锁定它,都将首先设置其值 . 如果同时,当第一个线程更改其值时,其他一些线程会出现并读取旧值,那么新线程是否会读取其旧值?

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

    AtomicInteger和volatile int有什么区别?

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

相关问题