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的自增没有原子性:

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在修改,那就继续尝试。所以这就保证了操作的原子性。

参考链接:http://www.cnblogs.com/Mainz/p/3556430.html

Java中的volatile关键字为什么不是不具有原子性的更多相关文章

  1. java中的volatile关键字

    java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...

  2. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  3. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  4. java中的Volatile关键字使用

    文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...

  5. 精通Java中的volatile关键字

    在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法. 很多资料中是这样介绍volatile关键字的: volatile是轻量级的sy ...

  6. CPU缓存一致性协议与java中的volatile关键字

    有关缓存一致性协议MESI自行百度. 提出问题:volatile在缓存一致性协议上又做了哪些事情?为啥它不保证原子性? 在缓存一致性协议下,CPU为了执行效率使用了写(存储)缓存和失效队列从而导致对用 ...

  7. zz剖析为什么在多核多线程程序中要慎用volatile关键字?

    [摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...

  8. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  9. 再议Java中的static关键字

    再议Java中的static关键字 java中的static关键字在很久之前的一篇博文中已经讲到过了,感兴趣的朋友可以参考:<Java中的static关键字解析>. 今天我们再来谈一谈st ...

随机推荐

  1. HTTPS 通俗简介

    为什么需要HTTPS 9个问题搞懂 https 来源 HTTP是明文传输的,也就意味着,介于发送端.接收端中间的任意节点都可以知道你们传输的内容是什么.这些节点可能是路由器.代理 等. 举个最常见的例 ...

  2. koa 微信小程序 项目

    这个微信号入门, 应该能自己模仿做一个微信公众号了 另外 微信小程序开发 和 微信公众号h5嵌入 还是有区别的 h5嵌入在体验上和 微信小程序 差距还是比较大, 因为小程序直接调用了微信的原生组件, ...

  3. I.MX6 HUAWEI MU609 3G porting

    /*************************************************************************** * I.MX6 HUAWEI MU609 3G ...

  4. vue 钩子

    生命周期总结 这么多钩子函数,我们怎么用呢,我想大家可能有这样的疑问吧,我也有,哈哈哈. beforecreate : 举个栗子:可以在这加个loading事件 created :在这结束loadin ...

  5. com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. 问题解决方法

    一.问题 今天用mybatis连接数据库时出现了如下错误: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The serve ...

  6. 实验吧—隐写术——WP之 欢迎来到地狱

    解压压缩包后出现三个文件.jpg .zip .docx 发现图片打开不显示,用winhex打开发现缺少文件头,将jpg的文件头补上 保存后图片可以打开 是一个百度网盘的链接,注意l和1的区别,网盘里是 ...

  7. spring boot 热部署devtools实现

    1.devtools spring为开发者提供了一个名为spring-boot-devtools的模块来使Spring Boot应用支持热部署,提高开发者的开发效率,无需手动重启Spring Boot ...

  8. k8s dockerk个人学习(2)

    部署nginx和应用 部署nginx时我们需要把nginx的配置文件那出来便于以后修改nginx的配置,放置nginx配置文件时我们用k8s的pv的存储方式来存放这些文件 1.安装nfs服务,在kub ...

  9. struts神马的不过是对servlet、filter的封装而已,hibernate神马的也不过是对jdbc的封装而已,他们只是把一些常见的操作流程化了,如果不懂servlet、filter,不懂jdbc,使用struts和hibernate出问题了都不知道是怎么回事。

    struts神马的不过是对servlet.filter的封装而已,hibernate神马的也不过是对jdbc的封装而已,他们只是把一些常见的操作流程化了,如果不懂servlet.filter,不懂jd ...

  10. 用newLISP通过SMTPserver发送邮件

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/sheismylife/article/details/26633073 直接使用标准模块smtpx. ...