对于内存模型的三大特性:有序性、原子性、可见性。

大家都知道volatile能保证可见性和有序性但是不能保证原子性,但是为什么呢?

一、原子性、有序性、可见性

1、原子性:

(1)原子的意思代表着——“不可分”;
(2)在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。原子性是拒绝多线程交叉操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。

2、可见性

线程执行结果在内存中对其它线程的可见性。

变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,加了这个指令后,会引发两件事情:

  • 发生修改后强制将当前处理器缓存行的数据写回到系统内存
  • 这个写回内存的操作会使得在其他处理器缓存了该内存地址无效,重新从内存中读取。

3、有序性

在本线程内观察,所有操作都是有序的(即指令重排不会导致单线程程序执行结果与排序前有任何差别)。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

二、线程安全的两个问题,执行控制和内存可见

执行控制(synchronize):控制代码只能顺序执行(执行一次只能被一个线程执行)或者可以多线程并发执行。

内存可见控制(volatile):线程执行结果在内存中对其它线程的可见性。线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。

volatile和synchronize两个关键字就是上述两种作用。

  • synchronize关键字使得同一时刻只有一个线程可以获得当前变量、方法、类的锁,其他线程无法访问,也就无法同步并发执行,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作,保障有序性、可见性、原子性;
  • volatile通过强制将当前线程修改后的值写回内存并使得其他线程中该值无效的方式保证其可见性,通过禁止指令重排的方式保证有序性,具体为何不能保证原子性在下一部分讨论。

三、为什么volatile不能保证原子性

对于i=1这个赋值操作,由于其本身是原子操作,因此在多线程程序中不会出现不一致问题,但是对于i++这种复合操作,即使使用volatile关键字修饰也不能保证操作的原子性,可能会引发数据不一致问题。

 private volatile int i = 0;
i++;

如果启了500条线程并发地去执行i++这个操作  最后的结果i是小于500的

 i++操作可以被拆分为三步:

      1,线程读取i的值

      2、i进行自增计算

      3、刷新回i的值

网上一些博客的解释是:

假设某一时刻i=5,此时有两个线程同时从主存中读取了i的值,那么此时两个线程保存的i的值都是5, 此时A线程对i进行了自增计算,然后B也对i进行自增计算,此时两条线程最后刷新回主存的i的值都是6(本来两条线程计算完应当是7)所以说volatile保证不了原子性。

我的不解之处在于:

既然i是被volatile修饰的变量,那么对于i的操作应该是线程之间是可见的啊,就算A.,B两个线程都同时读到i的值是5,但是如果A线程执行完i的操作以后应该会把B线程读到的i的值置为无效并强制B重新读入i的新值也就是6然后才会进行自增操作才对啊。

后来参照其他博客终于想通了:

1、线程读取i

2、temp = i + 1

3、i = temp

当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。

四、volatile和synchronized的区别

  1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  3. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

参考文献:

java内存模型:https://blog.csdn.net/suifeng3051/article/details/52611310

volatile与synchronize的区别:https://blog.csdn.net/it_manman/article/details/79497807

为什么volatile不能保证原子性:https://blog.csdn.net/xdzhouxin/article/details/81236356 https://blog.csdn.net/ACreazyCoder/article/details/82047970

为什么volatile能保证有序性不能保证原子性的更多相关文章

  1. volatile实现可见性但不保证原子性

    volatile实现可见性但不保证原子性 volatile关键字: 能够保证volatile变量的可见性 不能保证volatile变量复合操作的原子性 volatile如何实现内存可见性: 深入来说: ...

  2. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  3. threadlocal精髓是为每一个线程保证一个共享对象,保证一个,保证是同一个

    threadlocal精髓是为每一个线程保证一个共享对象,保证一个,保证同一个线程中是同一个共享对象. 如果是静态变量是共享的话,那必须同步,否则尽管有副本,还是会出错,故C错

  4. 轻量级的同步机制——volatile语义详解(可见性保证+禁止指令重排)

    目录 1.关于volatile 2.语义一:内存可见性 2.1 一个例子 2.2 java的内存模型(JMM) 2.3 happens-before规则 2.4 volatile解决内存可见性问题的原 ...

  5. Volatile和Synchronized对可见性和原子性的支持

    在学习并发编程的时候,遇见了volatile和synchronized关键字问题,volatile是可以保证可见性,但无法保证原子性,synchronized关键字由于其是加锁机制,肯定是可以保证原子 ...

  6. 【Java并发基础】并发编程bug源头:可见性、原子性和有序性

    前言 CPU .内存.I/O设备之间的速度差距十分大,为了提高CPU的利用率并且平衡它们的速度差异.计算机体系结构.操作系统和编译程序都做出了改进: CPU增加了缓存,用于平衡和内存之间的速度差异. ...

  7. 【Java进阶】并发编程

    PS:整理自极客时间<Java并发编程> 1. 概述 三种性质 可见性:一个线程对共享变量的修改,另一个线程能立刻看到.缓存可导致可见性问题. 原子性:一个或多个CPU执行操作不被中断.线 ...

  8. Java并发编程之验证volatile不能保证原子性

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  9. 为什么volatile不能保证原子性而Atomic可以?

    在上篇<非阻塞同步算法与CAS(Compare and Swap)无锁算法>中讲到在Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值 ...

随机推荐

  1. Spring 为啥默认把bean设计成单例的?

    熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton.prototype.request.session.global session. 如下图是官方文档上的截图, ...

  2. 12组-Alpha冲刺-4/6

    侯钦凯 过去两天完成了哪些任务 完善UI界面,复习考试 展示GitHub当日代码/文档签入记录 接下来的计划 复习考试,准备答辩 还剩下哪些任务 博客和答辩 燃尽图(团队整体) 遇到了哪些困难 在部分 ...

  3. MySQL基础语句(MySQL内置函数 )

    MySQL 字符串函数 函数 描述 实例 ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码. 返回 CustomerName 字段第一个字母的 ASCII 码: SELECT ASCI ...

  4. Django 小实例S1 简易学生选课管理系统 8 CSS样式优化

    Django 小实例S1 简易学生选课管理系统 第8节--CSS样式优化 点击查看教程总目录 作者自我介绍:b站小UP主,时常直播编程+红警三,python1对1辅导老师. 前面的几节下来,用户模块基 ...

  5. 0-pyqt介绍

    1.QT 的特点 2.QT的历史 3.搭建pyQT的开发环境 python  pyqt包  pycharm 4.搭建pyQT第一个应用 必须使用两个类:QApplication和QWidget.都在P ...

  6. [cf587D]Duff in Mafia

    二分最大边权,即有些边强制不能被选 接下来,即任意一点上某两边不能同时被选,以及任意一点上颜色相同的两边必须被选择一条 这些限制都可以用2-sat的形式来描述(强制不能选即连边"选-> ...

  7. [bzoj1927]星际竞速

    考虑没有爆发,那么相当于是带权最小不可交路径覆盖,由于只能从编号小的到编号大的,因此一定是DAG,而DAG的最小路径覆盖可以拆点并跑最大流,那么带权的只需要跑费用流即可(S向i连(1,0)的边,i'向 ...

  8. [atAGC046F]Forbidden Tournament

    称满足第1个条件的图为竞赛图,先来分析竞赛图 结论1:竞赛图点集上的导出子图也为竞赛图(证明略) 结论2:对于一张竞赛图,若不含有3元环,则该图为DAG 证明:反证法,若其不为DAG,设最小的简单环为 ...

  9. Java-ASM框架学习-java概念转字节码概念

    前言 当我们操作字节码的时候,都是和字节码的概念打交道,这让我们很困扰,asm也想到了这点,为了方便,它提供了一个可以把java概念转化为字节码概念的类 import org.objectweb.as ...

  10. 待办事项-redis

    package com.easyagu.liwei.util;import redis.clients.jedis.BinaryClient;import redis.clients.jedis.Je ...