文章目录

  • 1.概述
  • 2.产生并发Bug的源头
  • 3.线程切换带来的原子性问题
  • 三、编译优化带来的有序性问题#
  • 四、Java内存模型#
  • 五、Happens-Before规则
    • 5.1 规则1:程序的顺序性规则
    • 5.2 规则2:volatile变量规则#
    • 5.3 规则3:传递性#
    • 5.4 规则4:管程中锁的规则
    • 5.5 规则5:线程start()规则
    • 5.6 规则6:线程join()规则
  • 六、final

1.概述

转载:Java核心复习—— 原子性、有序性与Happens-Before

视频:

2.产生并发Bug的源头

  • 可见性 缓存导致的可见性问题
  • 原子性 线程切换带来的原子性问题
  • 有序性 编译优化带来的有序性问题

上面讲到了 volatile 与可见性,本章再主要讲下原子性、有序性与Happens-Before规则。

3.线程切换带来的原子性问题


count += 1 这一句高级语言的语句,往往需要多条CPU执令。可以分为3步:

  • 将count值加载到寄存器
  • 在寄存器中对count进行+1操作
  • 将count值写回内存


所以,我们需要在高级语言的层面上,确保一些操作是原子性操作。

三、编译优化带来的有序性问题#

编译器为了优化性能,有时会改变程序中语句的先后顺序。

a = 6;
b = 7;

经过编译优化后,可能会变成

b = 7;
a = 6

双重检查创建单例对象

public class Singleton{static Singleton instance;static Singleton getInstance(){if(instance == null){synchronized(Singleton.class){if(instance == null){instance = new Singleton();}}}}}

这个例子看似很完美,但其实是可能触发空指针异常。

为什么可能会触发空指针异常。

假设getInstance()的运行过程是这样:

  • 开辟一块M内存空间
  • 在M内存空间上创建Singleton对象
  • 将对象赋值给instance

这样的话是没问题的。但编译时并非按这个顺序来的,而是按照下面的顺序来:

  • 开辟一块M内存空间
  • 将M内存空间的地址赋值给instance
  • 在M内存空间创建Singleton对象。

当A线程走到了第2步,将M内空空间的地址赋值给instance时,发生线程切换,则B线程判断instance == null时,结果返回false,则返回null,导致返回空指针。

四、Java内存模型#

上面说到产生并发Bug的源头是缓存导致的可见性、编译优化导致的顺序性。如果禁用缓存和编译优化是不是就问题解决了,并不是,将引入最大的问题,程序性能问题。

合理的方案是按需求来禁用缓存和编译优化。

Java内存模型规范了按需禁用缓存和编译优化的方法(volatile、synchronized、final、Happens-Before规则)。


class VolatileExample{int x = 0;volatile boolean v = false;public void writer(){x = 42;v = true;}publci void reader(){if(v == true){//x = ?sout(x);}}}

上面的例子,A线程调用writer(),B线程调用reader(),B看到的x是多少?JDK1.5以前是0,JDK1.5及以上是42。

原因是JDK1.5对volatile进行增强,新增了Happens-Before规则。

五、Happens-Before规则

5.1 规则1:程序的顺序性规则

意思就是:前面一个操作的结果对后续操作是可见的。

x = 42 ; Hapens-Before于 v = true;

如果是在JDK1.5以前,v = true可能会先被执行。


class VolatileExample{int x = 0;volatile boolean v = false;public void writer(){x = 42;v = true;}publci void reader(){if(v == true){//x = ?sout(x);}}}

5.2 规则2:volatile变量规则#

对一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作。

5.3 规则3:传递性#

A Happens-Before B
B Happens-Before C

那么

A Happens-Before C

class VolatileExample{int x = 0;volatile boolean v = false;public void writer(){x = 42;v = true;}publci void reader(){if(v == true){//x = ?sout(x);}}}

规则2结合规则3和规则1来一起看。

x = 42 Happens-Before v = true,这是规则1

A线程写变量v=true Happens-Before B线程读变量v=true,这是规则2
由规则1、2结合规则3的传递性,得出x = 42 Happens-Before B线程读变量v=true

5.4 规则4:管程中锁的规则

对一个锁的解锁 Happens-Before于后续对这个锁的加锁。

管程是一种通用的同步原语。同步原语是什么。。。。synchronized是Java对管程的实现。


synchronized(this){//此处自动加锁// x 是共享变量,初始值=10if(this.x < 12){this.x = 12;}}//此处自动解锁

A执行完代码块后x=12,执行完释放锁。线程B进入代码块,能够看到A对x的写操作。

5.5 规则5:线程start()规则

主线程A启动子线程B后,子线程B能够看到主线程在启动子线程B之前的操作。

也就是 start()操作 Happens-Before 线程B中的任意操作。

Thread B = new Thread(() -> {//主线程调用B.start()之前//所有对共享变量的修改,此处可见//var == 77
})var = 77;
B.start();

5.6 规则6:线程join()规则

这条是关于线程等待的。主线程A等待子线程B完成(A调用子线程B的join()方法)。当子线程B完成后,主线程能够看到子线程的操作。

Thread B = new Thread(()-> {var = 66;})//一系列操作B.start();//进行一系列操作B.join()

线程B中的任意操作,Happens-Before 于该join()操作。

六、final

final修饰变量时,初衷是告诉编译器:这个变量生而不变,可以尽可能的优化。

那什么时候使用final呢?

一个答案就是“尽可能的使用”。任何你不希望改变的(基本类型,或者指向一个对象,不管该对象是否可变)一般来讲都应该声明为final。

另一种看待此问题的方式是:

如果一个对象将会在多个线程中访问并且你并没有将其成员声明为final,则必须提供其他方式保证线程安全

【java】Java 原子性、有序性与Happens-Before相关推荐

  1. java 并发原子性与易变性 来自thinking in java4 21.3.3

    java 并发原子性与易变性  具体介绍请參阅thinking in java4 21.3.3 thinking in java 4免费下载:http://download.csdn.net/deta ...

  2. [零基础学JAVA]Java SE实战开发-37.MIS信息管理系统实战开发[JDBC](1)

    MIS信息管理系统实战开发之使用MySQL实现保存 开发背景 ID.姓名.年龄为公共信息,而学生有成绩,工人有工资 定义一个抽象类Person(ID.姓名.年龄),学生是其子类,有成绩,工人是其子类有 ...

  3. 零基础学JAVA]Java SE基础部分-01. Java发展及JDK配置

    1.课程名称:Java发展及JDK配置 本季介绍了JAVA的发展过程,包括JDK的发展历程,path路径的配置和classpath的配置及作用.并简单讲解了一个简单的JAVA程序,并通过此程序讲解了J ...

  4. Java - Java集合中的安全失败Fail Safe机制 (CopyOnWriteArrayList)

    文章目录 Pre 概述 fail-safe的容器-CopyOnWriteArrayList add remove函数 例子 缺陷 使用场景 Pre Java - Java集合中的快速失败Fail Fa ...

  5. Java - Java集合中的快速失败Fail Fast 机制

    文章目录 什么是 fail-fast 源码解读 Itr 为什么对集合的结构进行修改会发生并发修改异常-源码分析 修改方法之 remove 修改方法之 add 案例分享 [案例一] [案例二] [案例三 ...

  6. java比python难_python java JAVA比python难多少?

    python和java哪个好? 学python好还是java好一次一次的失望后来渐渐放弃,哪怕后来有人提起你的名字小编脸上也不会泛起红晕. Python Python是一种面向对象的解释型计算机程序设 ...

  7. [Jmeter]通过批处理调用java,java从CSV动态读取登录的用户名和密码,并将其作为参数组合成字符串,写入外部.bat文件,然后通过Java执行这个外部批处理文件...

    问题1:怎样通过批处理调用java代码? 问题2:怎样通过java从CSV文件获取到用户名和密码存入变量? 问题3:怎样将获取到的用户名和密码组合成字符串,写入外部批处理文件? 问题4:怎样在批处理文 ...

  8. java java se_Java 8 SE可选,严格的方法

    java java se 大约两周前,Stephen Colebourne提出了使用Optional的实用方法 . 如果您阅读了它,您可能会从我以前的建议中猜到我不同意. 总览 我必须以免责声明开头, ...

  9. java java编码_Java编码约定被认为是有害的

    java java编码 在Oracle网站上有Java编程语言的官方代码约定指南. 您可能希望这份超过20页的文档将是有关Java语言的最佳实践,提示和技巧的最完整,最全面和最权威的来源. 但是一旦您 ...

  10. java java se_Java SE 11:推动Java前进

    java java se 介绍 在我看来,这篇文章提出了Java语言应该如何发展成为一种首选语言. 它还提供了一些我喜欢但是有时(可能永远不会)成为Java的一部分的功能,出于某些原因,我将对此加以解 ...

最新文章

  1. 使用js冒泡实现点击空白处关闭弹窗
  2. 博客园添加访问人数统计超详细教程
  3. gradlew wrapper使用下载到本地的gradle.zip文件装配--转
  4. [JOYOI] 1124 花店橱窗
  5. docker化你的java应用(上)
  6. Linux的Configure详情
  7. 提升领导力 六商是基础
  8. javascript四个方向无间隙滚动合集(多浏览器IE,firefox兼容)
  9. MySQL服务器硬件和操作系统调节
  10. php pdo 关闭,php pdo预处理
  11. [华为机试练习题]37.合唱队
  12. 模型人的因素_肺癌风险预测模型中纳入肺功能如何?
  13. 网络设备中的路由器的作用,如何设置路由器,上网、IP分配、黑白名单、访问量...
  14. 基于Simulink与FlightGear联合建模并仿真多旋翼无人机在平衡态的动态控制
  15. linux系统 安装主板驱动,I810 Graphics LINUX Driver的安装
  16. 形容计算机技术发展的词,形容技术发展的成语是什么_四字词语 - 成梦词典
  17. 判断闰年的3种方法(判断+范围输出)
  18. 开发可以自动运行程序的U盘
  19. 风影ASP.NET基础教学 13 NET3.0特性补充
  20. 判断字符串中是否仅包含英文字母、数字和汉字

热门文章

  1. 让每一首心动歌曲穿越人海遇见你,背后竟藏着这么多“黑科技”|回响·TME音乐公开课...
  2. 谁给小鹏P5的勇气?
  3. 4月份全球新注册39.2万辆电动汽车 榜首并非Model 3
  4. 2020魔幻“商战”:抢公章、发黄图、半夜翻工厂...最烂的小说都不敢这么写
  5. 王国权辞任中国电信股份有限公司执行副总裁
  6. 理想更新“货车并线预警”遭用户吐槽 李想:仍在优化
  7. iPhone 12发布前夜:富士康奖励万元招不到人,有工人国庆连轴转
  8. 能自定义桌面后,iOS用户玩疯了
  9. iOS版微信跟上了!已支持改微信号,修改后好友不会收到提醒
  10. 王自如、罗永浩将一起出镜直播带货?罗永浩亲自回应