挥发性Vs原子

Vai*_*hav 105 java volatile

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

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

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

这是否意味着,凡发生在它的锁,将要设置其值.如果在此期间,一些其他的线程来和读取旧值,而第一个线程正在改变它的价值,那么没有新的线程将读取其旧的价值?

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

Lou*_*man 129

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

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

AtomicAtomicInteger和的类AtomicReference以原子方式提供更多种类的操作,具体包括增量AtomicInteger.

  • @LouisWasserman:"`volatile`关键字的效果是......对该变量的操作是原子的." 我必须不同意这种说法的真实性.对Java中非8字节变量的单独读写始终是原子的.`volatile`关键字不会访问那些原本无法访问的变量.它只是强制处理环境立即刷新对变量所做的主内存更改,以便所有线程都可以看到这些更改. (20认同)
  • 要添加的东西,存储在`AtomicInteger`中的`int`是`private volatile int value;` (13认同)
  • @Vallentin:是的,但`AtomicInteger`使用一些特殊的实用程序 - 特别是包括`Unsafe.compareAndSwapInt` - 来安全地执行更强大的并发操作. (9认同)
  • @ortang:只是一个肯定.我检查了JLS(s17.7).你是正确的,除了提供线程间可见性之外,`volatile`声明还会对long和double(以其他方式也不会是原子)进行读写操作. (6认同)
  • 这只是事实的一半,你错过了非常重要的执行顺序问题.请参阅下面的解释. (2认同)
  • @LouisWasserman 原始读/写始终是原子的,只有“long”和“double”“需要”“易失性”是原子的。 (2认同)

Two*_*The 74

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

以Java中的两个线程为例:

线程A:

value = 1;
done = true;
Run Code Online (Sandbox Code Playgroud)

线程B:

if (done)
  System.out.println(value);
Run Code Online (Sandbox Code Playgroud)

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

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

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

请看以下示例:

i = i + 1;
Run Code Online (Sandbox Code Playgroud)

无论你如何定义i,在执行上述行时读取值的另一个Thread可能得到i或i + 1,因为该操作不是原子的.如果另一个线程将i设置为不同的值,在最坏的情况下,我可以将其设置回线程A之前的任何值,因为它只是在基于旧值计算i + 1的中间,然后设置i再次到那个旧值+ 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
Run Code Online (Sandbox Code Playgroud)

像AtomicInteger这样的原子学确保这种操作以原子方式发生.所以上面的问题不可能发生,一旦两个线程完成,我要么是1000或1001.


Try*_*ing 50

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

  1. 原子
  2. 能见度

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

  1. 将值复制到注册
  2. 增加它
  3. 把它放回去

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

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


sco*_*ttb 23

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

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

boolean isStopped = false;
    :
    :

    while (!isStopped) {
        // do some kind of work
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 谢谢,这个答案对我来说是一个更好的答案。.如此清晰。我从原始布尔型var中停止了一个带有简单状态值的线程,我想知道为什么它在运行时没有停止(有时确实如此)停)..., (2认同)

Ort*_*ier 13

使用volatile关键字:

  • 使非原子64位操作成为原子:longdouble.(所有其他的,原始访问已经保证是原子的!)
  • 保证其他线程+可见性效果可以看到变量更新:在写入volatile变量之后,在写入该变量之前可见的所有变量在读取相同的volatile变量(在排序之前发生)后变得对另一个线程可见.

java.util.concurrent.atomic.*根据java文档,这些类是:

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

boolean compareAndSet(expectedValue, updateValue);

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

来自java文档:

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

对于你的问题:

这是否意味着,凡发生在它的锁,将要设置其值.如果同时,当第一个线程更改其值时,其他一些线程会出现并读取旧值,那么新线程是否会读取其旧值?

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

AtomicInteger和之间有什么区别volatile int

AtomicInteger提供了一个原子操作int以适当的同步(例如incrementAndGet(),getAndAdd(...)...),volatile int将只保证的知名度int给其他线程.

  • Java Spec 17.7:`...对非易失性long或double值的单次写入被视为两个单独的写入:每个32位半写一次.这可能导致线程从一次写入看到64位值的前32位,而从另一次写入看到第二次32位的情况.volatile和long值的写入和读取始终是原子的.对引用的写入和读取始终是原子的,无论它们是实现为32位还是64位值.链接:https://docs.oracle.com/javase/specs/jls/se7/html/jls -17.html#JLS-17.7 (2认同)

Pet*_*rey 12

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

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

这是否意味着,凡发生在它的锁,将要设置其值.

没有锁.这synchronized是为了什么.

如果同时,当第一个线程更改其值时,其他一些线程会出现并读取旧值,那么新线程是否会读取其旧值?

是,

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

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

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