`

(转)为什么volatile不能保证原子性而Atomic可以?

    博客分类:
  • java
 
阅读更多

在上篇《非阻塞同步算法与CAS(Compare and Swap)无锁算法》中讲到在Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我觉得这个词在Java规范中从未被解释清楚的神奇关键词,在Sun的JDK官方文档是这样形容volatile的:

The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.

意思就是说,如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。这不是互相矛盾吗?

不要将volatile用在getAndOperate场合,仅仅set或者get的场景是适合volatile的

不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁),仅仅set或者get的场景是适合volatile的

volatile没有原子性举例:AtomicInteger自增

例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:

1
2
3
4
mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

注意最后一步是内存屏障。

什么是内存屏障(Memory Barrier)?

内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

内存屏障(memory barrier)和volatile什么关系?上面的虚拟机指令里面有提到,如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

volatile为什么没有原子性?

明白了内存屏障(memory barrier)这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。下面的测试代码可以实际测试voaltile的自增没有原子性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    private static volatile long _longVal = 0;
     
    private static class LoopVolatile implements Runnable {
        public void run() {
            long val = 0;
            while (val < 10000000L) {
                _longVal++;
                val++;
            }
        }
    }
     
    private static class LoopVolatile2 implements Runnable {
        public void run() {
            long val = 0;
            while (val < 10000000L) {
                _longVal++;
                val++;
            }
        }
    }
     
    private  void testVolatile(){
        Thread t1 = new Thread(new LoopVolatile());
        t1.start();
         
        Thread t2 = new Thread(new LoopVolatile2());
        t2.start();
         
        while (t1.isAlive() || t2.isAlive()) {
        }
 
        System.out.println("final val is: " + _longVal);
    }
 
Output:-------------
     
final val is: 11223828
final val is: 17567127
final val is: 12912109

volatile没有原子性举例:singleton单例模式实现

这是一段线程不安全的singleton(单例模式)实现,尽管使用了volatile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class wrongsingleton {
    private static volatile wrongsingleton _instance = null;
 
    private wrongsingleton() {}
 
    public static wrongsingleton getInstance() {
 
        if (_instance == null) {
            _instance = new wrongsingleton();
        }
 
        return _instance;
    }
}

下面的测试代码可以测试出是线程不安全的:

原因自然和上面的例子是一样的。因为volatile保证变量对线程的可见性,但不保证原子性

附:正确线程安全的单例模式写法:

1
2
3
4
5
6
7
8
9
@ThreadSafe
public class SafeLazyInitialization {
   private static Resource resource;
   public synchronized static Resource getInstance() {
      if (resource == null)
          resource = new Resource();
      return resource;
    }
}

另外一种写法:

1
2
3
4
5
@ThreadSafe
public class EagerInitialization {
  private static Resource resource = new Resource();
  public static Resource getResource() { return resource; }
}

延迟初始化的写法:

1
2
3
4
5
6
7
8
9
@ThreadSafe
public class ResourceFactory {
    private static class ResourceHolder {
        public static Resource resource = new Resource();
    }
    public static Resource getResource() {
        return ResourceHolder.resource ;
    }
}

二次检查锁定/Double Checked Locking的写法(反模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonDemo {
    private static volatile SingletonDemo instance = null;//注意需要volatile
  
    private SingletonDemo() {   }
  
    public static SingletonDemo getInstance() {
        if (instance == null) { //二次检查,比直接用独占锁效率高
               synchronized (SingletonDemo .class){
                    if (instance == null) {
                               instance = new SingletonDemo ();
                    }
             }
        }
        return instance;
    }
}

为什么AtomicXXX具有原子性和可见性?

就拿AtomicLong来说,它既解决了上述的volatile的原子性没有保证的问题,又具有可见性。它是如何做到的?当然就是上文《非阻塞同步算法与CAS(Compare and Swap)无锁算法》提到的CAS(比较并交换)指令。 其实AtomicLong的源码里也用到了volatile,但只是用来读取或写入,见源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AtomicLong extends Number implements java.io.Serializable {
    private volatile long value;
 
    /**
     * Creates a new AtomicLong with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicLong(long initialValue) {
        value = initialValue;
    }
 
    /**
     * Creates a new AtomicLong with initial value {@code 0}.
     */
    public AtomicLong() {
    }

其CAS源码核心代码为:

1
2
3
4
5
6
7
8
9
int compare_and_swap (int* reg, int oldval, int newval)
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval)
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

虚拟机指令为:

1
2
3
4
mov    0xc(%r11),%eax       ; Load
mov    %eax,%r8d           
inc    %r8d                 ; Increment
lock cmpxchg %r8d,0xc(%r11) ; Compare and exchange

因为CAS是基于乐观锁的,也就是说当写入的时候,如果寄存器旧值已经不等于现值,说明有其他CPU在修改,那就继续尝试。所以这就保证了操作的原子性。

ConcurrencyCAS

 

 

 

 

 

 

REFS:http://www.cnblogs.com/Mainz/p/3556430.html

分享到:
评论

相关推荐

    volatile,nonatomic和atomic关键字测试

    然而,`volatile`并不保证操作的原子性,所以它不能单独解决并发问题。 接着,`nonatomic`和`atomic`与Objective-C的属性有关,它们定义了属性赋值和取值操作的线程安全性。默认情况下,Objective-C的属性是`atomic...

    atomic_ops原子操作1

    这个初始化操作是原子的,即在 runtime 之前进行初始化,可以保证正确地反映初始化的值。如果在 runtime 进行初始化,需要在读取值之前使用隐式或显式的读内存屏障。 atomic_t 的设置 atomic_t 的设置使用 `atomic...

    atomic_ops.pdf

    一些原子操作可能需要特定的CPU指令来保证其原子性,而在某些平台上这些指令可能不存在或者不能直接访问。在这种情况下,开发者需要利用更高级的同步机制,或者确保在支持的硬件上进行操作。 最后,文档中提到,...

    synchronized ReentrantLock volatile Atomic 原理分析.docx

    ReentrantLock分为公平锁和非公平锁,公平锁保证了线程按照等待顺序获取锁,而非公平锁则不保证这一特性。 ### 3. volatile关键字 volatile关键字用于保证变量的可见性和有序性。它确保了当一个线程修改了volatile...

    java中volatile不能保证线程安全(实例讲解)

    这是因为 volatile 关键字只能保证变量的可见性,而不能保证操作的原子性。 如果我们想要保证线程安全,我们需要使用 synchronized 关键字来同步代码块。例如,在上面的实例中,我们可以使用 synchronized 关键字来...

    volatile的使用1

    3. **原子性**:volatile不能保证复合操作的原子性,例如,`x++`操作就不是原子性的。如果需要原子性操作,可以使用`synchronized`或`java.util.concurrent.atomic`包中的原子类。 4. **应用场景**:volatile适合...

    java 并发操作之原子性与可视性1

    `volatile`关键字提供了可见性保证,但不保证原子性,因此在设计并发程序时,我们需要根据实际情况选择合适的数据同步机制,如`synchronized`、`volatile`、`Atomic`类等,以确保程序的正确性和性能。

    Java多线程中提到的原子性和可见性、有序性1

    普通变量的更新需要主动同步回主内存,而volatile变量的修改会立即同步到主内存,并且读取时会从主内存刷新,因此volatile保证了变量的可见性。除此之外,`synchronized`和`final`关键字也能实现可见性。...

    解开Volatile的面纱V1.1

    但需要注意的是,`volatile`关键字并不能保证复合操作(如i++)的原子性,对于这类需求,应考虑使用`synchronized`或者`java.util.concurrent.atomic`包中的原子类。 总结来说,`volatile`关键字是Java并发编程中的...

    volatile关键字使用

    需要注意的是,`volatile`关键字并不意味着线程安全,它只保证了可见性,而没有提供原子性、有序性等其他并发控制所需的属性。在编写多线程程序时,如果需要更高级别的同步保障,还需要使用锁、原子变量(如C++11中...

    10 有福同享,有难同当—原子性.pdf

    1. 原子性:原子性保证了一个操作或者一系列操作在执行过程中不会被其他线程打断,即操作要么全部完成,要么全部不完成,不会出现部分完成的情况。在数据库事务处理中,原子性是ACID(原子性、一致性、隔离性、持久...

    18Java内存模型:Java中的volatile有什么用?1

    总之,`volatile`关键字在Java中起到了保证并发环境下变量可见性和有序性的作用,但并不能保证操作的原子性。在设计多线程程序时,理解`volatile`的工作原理及其限制对于正确地处理并发问题至关重要。

    java代码-volatile原子性

    总的来说,`volatile`关键字在Java中用于解决多线程环境下的可见性和有序性问题,但不能保证原子性。在编写多线程程序时,我们需要结合使用`volatile`、`synchronized`以及原子类来实现线程安全的数据访问,以确保...

    Java内存模型--原子性;有序性;可见性1

    原子性是指一个操作或多个操作被视为一个不可分割的整体,即这些操作要么全部完成,要么都不完成。在Java中,为了保证原子性,提供了两种主要的工具: - **synchronized关键字**:它可以保证对共享变量的访问是互斥...

    Java中volatile关键字的总结.docx

    虽然`volatile`关键字可以提供一定程度的同步和可见性,但它并不保证原子性。因此,当操作需要原子性时,单独使用`volatile`是不够的。比如,如果一个变量被多个线程同时读写,并且读写操作之间存在依赖关系,那么`...

    6vc中volatile关键字的应用共4页.pdf.zip

    它只能保证可见性,不能保证原子性。例如,在多线程环境中,如果两个线程同时修改一个`volatile`变量,仍然可能出现竞态条件。在这种情况下,还需要借助互斥锁等同步机制来保证数据的一致性。 此外,`volatile`也不...

    14、深入理解并发可见性、有序性、原子性与JMM内存模型(1).pdf

    1. **使用`synchronized`关键字**:它可以确保同一时刻只有一个线程可以执行指定的代码块,从而保证了原子性和可见性。 2. **使用`Lock`接口**:通过显式地锁定和解锁来实现对资源的独占访问。 3. **使用`volatile`...

    深入探讨Java多线程中的volatile变量共6页.pd

    例如,如果你有一个计数器,你不能简单地声明为volatile,因为++操作不是原子性的,需要使用synchronized或java.util.concurrent.atomic包下的原子类来确保线程安全。 在实际应用中,volatile常用于简单的状态标记...

    深入讲解C语言编程中volatile修饰符的作用

    声明为`volatile`确保对这些寄存器的读写操作不被优化,保证了与硬件交互的正确性。 `volatile`并不保证原子性。在多处理器系统中,对内存的读写操作可能不是原子的,特别是在并行访问时。在x86架构中,需要使用`...

Global site tag (gtag.js) - Google Analytics