目录

  • 一、原子性
  • 二、可见性
    • 1、串行
    • 2、单核CPU
    • 3、多线程多CPU时的可见性问题
    • 4、看下面一段代码,猜猜看删除结果
  • 三、有序性
  • 四、解决方案
    • Java高并发编程实战系列文章
    • 哪吒那些年写过的优秀文章

一、原子性

原子性指操作在CPU执行的过程中,不可中断,也不可在中途切换,要么执行完成、要么不执行。

简单的分析一下原子性问题,写一段大众代码,如下:

package com.nezha.thread;/*** @Autor 哪吒* @Date 2022-09-10*/
public class ThreadAtomicityTest {private int step;public int getStep(){return step;}public void increaseStep(){step++;};
}

看不出什么问题,都这么写啊。

使用JDK自带的javap查看一下程序的指令码:

D:\MyProject\target\classes\com\nezha\thread>javap -c ThreadAtomicityTest.class
Compiled from "ThreadAtomicityTest.java"
public class com.nezha.thread.ThreadAtomicityTest {public com.nezha.thread.ThreadAtomicityTest();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic int getStep();Code:0: aload_01: getfield      #2                  // Field step:I4: ireturnpublic void increaseStep();Code:0: aload_01: dup2: getfield      #2                  // Field step:I5: iconst_16: iadd7: putfield      #2                  // Field step:I10: return
}

重点看一下increaseStep的指令码,大概包含三大步骤:

  1. 将变量step从内存中加载到CPU的寄存器中;
  2. 在CPU的寄存器中执行step++操作;
  3. 将step++后的结果写入缓存(CPU缓存或计算机内存);

线程切换可能发生在任何一条指令完成之后,而不是Java某条语句完成后。

假设线程1和线程2同时执行increaseStep()方法,在线程1执行过程中,CPU完成指令码的步骤①后发生了线程切换,此时线程2开始执行指令码的步骤①。
当两个线程都执行完整个increaseStep()方法后,得到的step的值是1而不是2。Why is this?

如图所示,线程1将step=0加载到CPU的寄存器后,发生了线程切换。此时还没有执行step++操作,也没有将操作的结果写入内存,所以,内存中的step值仍为0。

线程2将step=0加载到CPU的寄存器中,执行step++操作,并将执行后的结果写入内存。此时,CPU切换到线程1继续执行,在执行线程1中的step++后,线程1中的step仍为1,线程1将step=1写入内存,最终内存中的step为1。

如果在CPU中存在正在执行的线程,此时,发生了线程切换,就可能导致并发编程的原子性问题。

所以,造成原子性问题的根本原因是在线程执行过程中发生了线程切换。

二、可见性

可见性指一个线程修改了共享变量,其它线程能够立刻读到共享变量的最新值。

在并发编程中,有两种情况能实现当一个线程修改了共享变量后,其它线程立刻就能读到最新值。

1、串行


线程1和线程2是串行执行的,线程1写完数据后,线程2会从主内存中读取数据。线程1向主内存中写入数据对线程2是可见的,所以线程1和线程2之间不存在可见性问题。

2、单核CPU

在单核CPU中,多个线程之间也不会出现可见性问题。


在单核CPU中,只能有一个线程占用CPU资源来执行任务。当其它线程抢占CPU执行任务时,共享变量中的值一定是最新的。

3、多线程多CPU时的可见性问题

Java中,多个线程在读写内存中的共享变量时,会先把主内存中的共享变量数据复制到线程的工作内存中。每个线程在对数据进行读写操作时,都是直接操作自身的工作内存中的数据。由于每个线程都有自己的工作内存,所以线程1的数据对线程2是不可见的。线程1修改了数据,线程2不一定能够立刻读到修改后的值,这就造成了可见性问题。

4、看下面一段代码,猜猜看删除结果

package com.guor.demo.sync;public class SynchronizedTest {private static int count = 0;public static void  incremetCount(){count++;}public static int increment() throws InterruptedException {Thread thread1 = new Thread(()->{for (int i = 0; i < 1000; i++) {incremetCount();}});Thread thread2 = new Thread(()->{for (int i = 0; i < 1000; i++) {incremetCount();}});// 启动线程thread1.start();thread2.start();thread1.join();thread2.join();return count;}public static void main(String[] args) throws InterruptedException {System.out.println(SynchronizedTest.increment());}
}

控制台输出:


为什么呢?

因为多个线程同时调用incremetCount()方法,出现了线程安全问题。

所以,造成可见性问题的根本原因是CPU缓存机制。

三、有序性

有序性指程序能够按照编写的代码顺序执行,不会发生跳过代码行的情况,也不会出现跳过CPU指令的情况。

那么什么时候会出现有序性问题呢?

为了提高程序的执行性能和编译性能,计算机和编译器有时候会修改程序的执行顺序。

在Java中一个典型的案例就是使用双重检测机制来创建单例对象。

package com.nezha.thread;/*** 线程不安全的单例模式*/
public class SingleInstance {private static SingleInstance instance;public static SingleInstance getInstance(){if(instance == null){synchronized (SingleInstance.class){if(instance == null){instance = new SingleInstance();}}}return instance;}
}

如果编译器和解释器不对上面的代码进行优化,也不改变程序的执行顺序,则代码的执行流程如下图所示:


如上图所示,假如线程1和线程2同时调用getInstance()方法获取对象实例,两个线程会同时发现instance为空,同时对SingleInstance.class加锁,而JVM会保证只有一个线程获取到锁,这里我们假设线程1获取到锁,线程2因为未获取到锁而进行等待。接下来,线程1再次判断instance对象为空,从而创建instance对象的实例,然后释放锁。此时,线程2被唤醒,再次尝试获取锁,获取锁成功后,线程2检查此时的instance对象已经不再是空,线程2不再创建instance对象。

上述流程看起来没有什么问题,但是,在高并发、大流量的场景下获取instance对象时,使用new关键字创建SingleInstance类的实例对象时,会因为编译器或解释器对程序的优化而出现问题。也就是说,问题的根源在于如下代码:

```instance = new SingleInstance();````

对于上面的代码包含三个步骤:

① 分配内存空间
② 初始化对象
③ 将instance引用指向内存空间

正常执行的CPU指令顺序为①②③,CPU对程序进行重排序后的执行顺序是①③②,此时就会出现问题。


如上图所示,当线程1判断instance为空时,为对象分配内存空间,并将instance指向内存空间。此时还没有进行对象的初始化,发生了线程切换,线程2获取到CPU资源执行任务。线程2判断此时的instance不为空,则不再执行创建对象的操作,直接返回未初始化的instance对象。

所以,造成有序性问题的根本原因是编译器对程序进行优化,从而可能造成有序性问题。

四、解决方案

在Java中解决原子性问题的方案包括synchronized、Lock、ReentranLock、ReadWriteLock、CAS操作、Java中提供的原子类等。

解决可见性和有序性问题,可以禁用CPU缓存和编译器优化。

JVM提供了禁用缓存和编译优化的方法,包括volatile关键字、synchronized、final关键字以及Java内存模型中的Happens-Before原则。

Java高并发编程实战系列文章

Java高并发编程实战1,那些年学过的锁

哪吒那些年写过的优秀文章

Java学习路线总结,搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

【Java基础知识 1】Java入门级概述

Java学习路线总结(思维导图篇)

SQL性能优化的21个小技巧

Java高并发编程实战2,原子性、可见性、有序性,傻傻分不清相关推荐

  1. Java 高并发_JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过!...

    JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过! 1.JPG (37.82 KB, 下载次数: 0) 2018-12-3 09:40 上传 2.JPG (28 ...

  2. Java高并发编程 (马士兵老师视频)笔记(一)同步器

    本篇主要总结同步器的相关例子:包括synchronized.volatile.原子变量类(AtomicXxx).CountDownLatch.ReentrantLock和ThreadLocal.还涉及 ...

  3. 29W 字总结阿里 Java 高并发编程:案例 + 源码 + 面试 + 系统架构设计

    下半年的跳槽季已经开始,好多同学已经拿到了不错的 Offer,同时还有一些同学对于 Java 高并发编程还缺少一些深入的理解,不过不用慌,今天老师分享的这份 27W 字的阿里巴巴 Java 高并发编程 ...

  4. Java 7并发编程实战手册

    2019独角兽企业重金招聘Python工程师标准>>> Java 7并发编程实战手册 本书是 Java 7 并发编程的实战指南,介绍了Java 7 并发API 中大部分重要而有用的机 ...

  5. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  6. java 并发集合_《Java 7并发编程实战手册》第六章并发集合

    由人民邮电出版社出版的<Java 7并发编程实战手册>终于出版了,译者是俞黎敏和申绍勇,该书将于近期上架.之前并发编程网组织翻译过此书,由于邮电出版社在并发网联系他们之前就找到了译者,所以 ...

  7. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

  8. Java 高并发第二阶段实战---高并发设计模式,内存模型,CPU一致性协议,volatile关键字剖析

    第二阶段的课程主要围绕着Volatile关键字,内存重排序,Happen-Before,Cpu一致性协议,高并发下的设计模式以及类加载器几个大的方面展开,下面是内容详细信息,本教程是本人录制,下载地址 ...

  9. @冰河老师的巨作,人手一册的Java高并发编程指南,值得了解一下

    还真不好意思,这次 Java Thread Pool 惊爆了! 高并发是每一个后端开发工程师都头疼的问题,也是工程师能力的分水岭.要想基于JDK核心技术,玩转高并发编程,必须要好好修炼内功才行. 文章 ...

  10. Java高并发编程:活跃性危险

    Java高并发程序中,不得不出现资源竞争以及一些其他严重的问题,比如死锁.线程饥饿.响应性问题和活锁问题.在安全性与活跃性之间通常存在依赖,我们使用加锁机制来确保线程安全,但是如果过度地使用加锁,则可 ...

最新文章

  1. CMS垃圾收集器小实验之CMSInitiatingOccupancyFraction参数
  2. 计算机教案word格式模板,用自定义模板编辑教案
  3. 箭头函数中的this的使用
  4. 二十八、Pyspider 爬取链家网
  5. 文末福利 | 国际前沿算法峰会报名进行中,几大亮点抢先看
  6. 100多千克的柠檬电池车可以开吗?
  7. SharePoint 2013的100个新功能之网站管理(一)
  8. 结构设计模式 - Bridge设计模式
  9. 【CV实战】Ubuntu18.04源码编译安装opencv-3.4.X+测试demo
  10. python的string模块
  11. Java基础(三)--final关键字
  12. python学习笔记第三节
  13. 结合Control.FirefoxDialog控件,构造优秀的参数配置管理模块
  14. golang中的URL 的编码和解码(转)
  15. 整理PC端微信文件夹
  16. Android 应用在后台弹出提示相关的笔记1
  17. 2019年全国大学生电子设计竞赛综合测评
  18. 一张专家推荐的最健康的作息时间表
  19. pandas 取某一列数据的几种形式比较
  20. 【ROS2学习】二、用python编写publisher和subscriber

热门文章

  1. 明解c语言 练习,《明解C语言》示例代码和练习代码[第8章]
  2. 转速恒压频比交流变频调速系统Simulink仿真,可观察到电压频率的变比情况以及电动机的转速波形
  3. 主流数据库优缺点以及性能分析
  4. 安川机器人位置变量要素_安川机器人位置型变量操作方法详细步骤解析
  5. python 英语分词_Python实现中英文分词
  6. JavaScript+随机选号
  7. android 周月切换日历,vue-week-picker实现支持按周切换的日历
  8. (二)Latex编译与xelatex编译
  9. 关于威联通升级5.0系统无法登录的解决办法(有用)
  10. vue如何集成阿里云视频服务组件(aliplayer)视频功能是使用el-dialog 弹出aliplayer播放