小陈:上一篇说了JAVA内存模型,但是后面说了在多线程并发操作的时候有可见性问题,我现在迫不及待想知道线程安全的可见性、原子性、有序性是啥了

老王:哈哈,可以。我先说说我自己对可见性、有序性、原子性的理解:

可见性

上一篇讲了,多个线程同时对某一个共享变量进行操作的时候,存在线程A的操作对线程B不可见的问题。简单来说就是线程A执行了某些操作对数据进行了变更;但是线程B并不知道,所以还是使用旧数据干它自己的活。

小陈:这么讲,按照概念来理解我还是很模糊啊,能不能搞个例子来讲解一下?

老王,没问题,我就结合上次你提的那个JAVA内存模型可能导致数据不一致的这个例子给你讲解一下

比如线程A线程B都执行x++操作(x的初始值是0),线程A执行完了之后将主内存的值更新为1,但是线程B由于已经将 x = 0 读取进入自己的工作内存了不知道线程A将x更新为1了,所以还是使用x=0去进行++操作。

像这种,就是典型的可见性问题,就是线程A操作了数据,但是线程B不可见,感知不到

小陈:嘿嘿,看来上次我的猜测没错啊,无论是CPU缓存架构下还是JAVA内存模型都是有可见性的问题。

老王:没错,你说的这个问题是存在的,但是还是有些手段可以避免的,后面我们再来讨论。下面我们再来说一下有序性的问题

有序性

有序性是指由于JIT动态编译器、操作系统为了给提高程序的执行效率,可能会对按顺序书写好的指令进行重排线程或者CPU执行的时候不一定按照程序书写的顺序来执行

比如程序的书写顺序是 指令1 -> 指令2 -> 指令3;但是由于指令重排序,某个线程执行这几个指令的时候,比如说线程A执行的时候,可能先执行指令3,然后再执行指令2、指令1。导致别的线程,比如说线程B看到线程A的指令执行是乱序的

我搞个代码给你讲解一下:

线程A在执行数据库、http客户端初始化工作,初始化完毕之后将initOk初始化表示置为true表示初始化完毕。

// 步骤1
dataSource = initDataSource();
// 步骤2
httpClient = initHttpClient();
// 步骤3
initOK = true;

线程B在这里一直监听线程A是否初始化资源完毕,看到initOK标识为true表示初始化结束。开始执行业务操作,获取数据,根据数据发起网络调用

// 步骤4
while(!initOK) {
}
// 步骤5
Object data = dataSource.getData();
// 步骤6
httpClient.request(data);

上面这段代码,正常来说线程A执行顺序应该是 步骤1 -> 步骤2 -> 步骤3。但是由于JIT动态编译器或者操作系统可能对指令进行重排序,所以可能执行顺序是 步骤3 -> 步骤1 -> 步骤2

这样就会导致线程B先看到了initOk = true,这样就会导致线程B直接跳出while循环,跳出等待,执行dataSource.getData方法,执行httpClient.request()方法;但是线程A的步骤1、步骤2还没执行dataSource、httpClient是null,会抛出空指针异常

小陈:等等,我来理解一下;线程A先执行了initOK = true导致线程B跳出了while循环,然后调用dataSource.getData方法,由于线程A还没执行dataSource = initDataSource()方法,所以dataSource对象可能是null值,这样线程B调用的时候可能抛出空指针异常,是这样吧?

老王:没错,理解得非常好,小陈你果然聪明啊;你这个理解力,我对后面讲解的文章越来越有信心了。

小陈:嘿嘿......

老王:上面这种有序性问题,在多线程并发执行的时候,由于指令的重排序存在,很可能是会发生的。

这就是有序性带来的线程安全问题,也就是线程B看到线程A的执行时乱序的,也就是不是按照步骤1、2、3这样顺序的来执行。

简单点来讲就是线程A还没初始化好,就将标识initOk设置为true。导致线程B误以为线程A搞定了,然后去获取数据,发起http请求,然后...,然后线程B就挂了...(线程B:线程A这坑爹的,还没初始化好就告诉我搞定了,这不是坑我嘛...)

老王:说了可见性、有序性的问题,下面我们再来说说原子性问题。

原子性

老王:原子性是说某个操作是不可分割的、不可中断的。

小陈:这个不可分割、不可中断是啥意思?

老王:

比如之前说的JAVA内存模型定义的8中操作;read、load、use、assign、store、write、lock、unlock等八种指令都是原子的。

老王:比如说read指令,不可分割:说的是这条指令是读取数据最小的指令了,不能再拆分成更多的指令

小陈:不可分割是不是说它就是最小的执行单元了,不能被拆分的意思?

老王:没错,就是这个意思.....

小陈:哦哦,这个不可分割我懂了,那不可中断又是啥玩意?

老王:简单来讲就是不能执行到一半就不干了,比如这个read指令,你不能读取一个变量的数据,只读取到一半的时候就撂挑子不干了;要执行就一起全部执行,不能干了一半就不干了,同时也不能被其它外部的因素打断了。

小陈:那意思是说cpu执行read指令,执行到一半的时候,就把这个线程挂起来,这个是不被允许的咯。

老王:哈哈,就是这样的。要干就全部都干了,不能中途搞了一半你跟我说退出了....

小陈:老王你真牛逼,这么晦涩的东西都被你三言两句简单的话就给说清楚了。

老王:那是,毕竟我可是单身十多年...;不,是工作十多年的老兵了,“技巧”早就磨练的杠杠的......

小陈:......

小陈:道理我是听明白了,实际编码里面那些操作是原子的,那些不是原子的呢?

老王:给你讲讲下面的例子就知道了:

比如下面的操作:

(1)y = 1;

(2)x++;

(3)z = y;

(1)其中y = 1操作是原子的,因为只是执行了load操作,将1直接loady,只有一条指令的执行。

(2) x++操作就不是原子性的,之前画图讲解过,i++操作经过,read、load、use、assign、store、write六个操作;虽然每个指令都是原子的,但是合并起来并不是原子的

比如说线程A执行readload操作将工作内存的变量x的值载入自己工作内存的变量副本中。但是还没来得及执行后续的use、assign、store、write指令,这个时候线程A就被挂起了

线程A被挂起期间线程B就也执行了read、load指令变量x放入线程B的工作内存里了。这就相当于线程A的这6条指令没有连续执行完,被中断了,中途CPU又去执行别的指令了,并不是不可分割、不可中断的。

(3)z = y 也不是原子的,它先要执行read指令读取y的值,然后执行load执行赋值给z。并不是单一的原子指令

小陈:哇塞,老王你太牛逼了,你这么说我全懂了。

小陈:既然多线程并发操作的时候会有这些问题,那操作系统或者说JAVA底层是怎么解决这些问题达到并发安全的效果的呢?

老王:操作系统设计者肯定是会想到这些问题的,这就是我们下面要慢慢讲解的话题了,操作系统或者JAVA底层是怎么解决这些并发安全的问题的。

老王:小陈,给你个任务,你去看看MESI一致性协议的内容,下面我们讲解一下MESI一致性协议,以及MESI一致性协议是如何解决可见性问题的。

关注小陈,公众号上更多更全的文章

JAVA并发文章目录(公众号)

JAVA并发专题 《筑基篇》

1.什么是CPU多级缓存模型?

2.什么是JAVA内存模型?

3.线程安全之可见性、有序性、原子性是什么?

4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

JAVA并发专题《练气篇》

5.volatile怎么保证可见性?

6.什么是内存屏障?具有什么作用?

7.volatile怎么通过内存屏障保证可见性和有序性?

8.volatile为啥不能保证原子性?

9.synchronized是个啥东西?应该怎么使用?

10.synchronized底层之monitor、对象头、Mark Word?

11.synchronized底层是怎么通过monitor进行加锁的?

12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

13.synchronized怎么保证可见性、有序性、原子性?

JAVA并发专题《结丹篇》

14. JDK底层Unsafe类是个啥东西?

15.unsafe类的CAS是怎么保证原子性的?

16.Atomic原子类体系讲解

17.AtomicInteger、AtomicBoolean的底层原理

18.AtomicReference、AtomicStampReference底层原理

19.Atomic中的LongAdder底层原理之分段锁机制

20.Atmoic系列Strimped64分段锁底层实现源码剖析

JAVA并发专题《金丹篇》

21.AQS是个啥?为啥说它是JAVA并发工具基础框架?

22.基于AQS的互斥锁底层源码深度剖析

23.基于AQS的共享锁底层源码深度剖析

24.ReentrantLock是怎么基于AQS实现独占锁的?

25.ReentrantLock的Condition机制底层源码剖析

26.CountDownLatch 门栓底层源码和实现机制深度剖析

27.CyclicBarrier 栅栏底层源码和实现机制深度剖析

28.Semaphore 信号量底层源码和实现机深度剖析

29.ReentrantReadWriteLock 读写锁怎么表示?

30. ReentrantReadWriteLock 读写锁底层源码和机制深度剖析

JAVA并发专题《元神篇》并发数据结构篇

31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?

32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?

33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?

34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?

35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?

36.DelayQueue 底层源码剖析,延时队列怎么实现?

37.SynchronousQueue底层原理解析

JAVA并发专题《飞升篇》线程池底层深度剖析

38. 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?

39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?

40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?

41. ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?

42. ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?

43. ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?

44. ThreadPoolExecutor shutdown、shutdownNow内部核心流程

45. 再回头看看为啥不推荐Executors提供几种线程池?

46. ThreadPoolExecutor线程池篇总结

3.线程安全之可见性、有序性、原子性是什么?相关推荐

  1. java内存模型 原子性_Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)...

    JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...

  2. java计算时间差_JAVA并发编程三大Bug源头(可见性、原子性、有序性),彻底弄懂...

    原创声明:本文转载自公众号[胖滚猪学编程]​ 某日,胖滚猪写的代码导致了一个生产bug,奋战到凌晨三点依旧没有解决问题.胖滚熊一看,只用了一个volatile就解决了.并告知胖滚猪,这是并发编程导致的 ...

  3. java 类型不可视_jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)

    简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描述多处理器系统能获得的运算加速能力. 摩尔定律(Moore):该定律用于描述处理器晶体管数量与运行效率间的发展关系. 当价 ...

  4. 可见性、原子性和有序性问题

    可见性.原子性和有序性问题 并发编程背景 核心矛盾 这些年,我们的 CPU.内存.I/O 设备都在不断迭代,不断朝着更快的方向努力.但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速 ...

  5. Java并发编程:可见性、原子性和有序性问题

    Java并发编程:可见性.原子性和有序性问题 前言 并发程序幕后的故事 源头之一:缓存导致的可见性问题 源头之二:线程切换带来的原子性问题 源头之三:编译优化带来的有序性问题 总结 前言 不管是哪一门 ...

  6. java原子性是什么,java 原子性 可见性 有序性

    原子性 原子性是指一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行. 如向变量x赋值操作 x = 10 是原子性的,就不会出现赋值操作进行到一半(x的低16位赋值成功,高16位没有赋 ...

  7. 【漫画】JAVA并发编程三大Bug源头(可见性、原子性、有序性)

    原创声明:本文转载自公众号[胖滚猪学编程]​ 某日,胖滚猪写的代码导致了一个生产bug,奋战到凌晨三点依旧没有解决问题.胖滚熊一看,只用了一个volatile就解决了.并告知胖滚猪,这是并发编程导致的 ...

  8. 原子性 可见性 有序性_极简主义的内容可见性

    原子性 可见性 有序性 A couple of years ago, Minimalism as a concept took over the design world. 几年前, 极简主义作为一种 ...

  9. 并发编程-06线程安全性之可见性 (synchronized + volatile)

    文章目录 线程安全性文章索引 脑图 可见性定义 导致不可见的原因 可见性 -synchronized (既保证原子性又保证可见性) 可见性 - volatile(但不保证操作的原子性) volatil ...

最新文章

  1. Excel万年历的制作
  2. 自定义logback触发器策略进行日志滚动
  3. 六、递归(Recursion)
  4. WCF 第十三章 可编程站点 使用WebGet和WebInvoke
  5. 战棋类中实现的移动范围
  6. 杭州湾跨海大桥视频上云,夯实智慧高速“云基建
  7. 研究机构预计芯片短缺将导致全球轻型汽车今年减产502万辆
  8. PHP的几个常用加密函数
  9. 找三元环(bzoj 3498: PA2009 Cakes)
  10. 测试先知和启发式方法
  11. mysql日期相减返回月数_MySql日期相减返回月数_MySQL
  12. delphi显示jpg、png、gif图片
  13. 18.网络技术——BGP的原理+实验题(后附练习题)
  14. Missionaries from the global south try to save the godless West
  15. 统计学基础——常用的概率分布(二项分布、泊松分布、指数分布、正态分布)
  16. python与r语言处理excel数据_R语言 | 读写txt、csv、excel文件
  17. byfen网java_用java编写程序根据考试成绩的等级打印出百分制分数段
  18. python 行列式_python 中leastsq 中的雅可比行列式的作用是什么
  19. 全国超级计算机排名500强每年发布几次,全球超级计算机500强榜单发布,中国排第几?...
  20. 自己定义ViewGroup实现仿淘宝的商品详情页

热门文章

  1. Win11怎么连接宽带?
  2. 8DOER: Dual Cross-Shared RNN for Aspect Term-Polarity Co-Extraction(2020.10.22)
  3. Android测试能不能用monk,使用Monkey对apk做稳定性测试
  4. 声明一个Tree(树)类,有成员ages(树龄),成员函数grow(int years)用以对ages 加上years,showage( )用以显示tree对象的ages值。在主函数中定义Tree类对
  5. A. Banana 2017 ACM-ICPC 亚洲区(乌鲁木齐赛区)网络赛
  6. java编一个求圆柱表面积_Java:输入圆柱体的半径和高,计算并输出圆柱体的体积和表面积...
  7. Java多态(面试考点,不要因为基础而忽视)
  8. 被忽视的设计:中文字体-oooooohmygosh
  9. 中国古代地方官制------地方行政机构之沿革简释
  10. 分析早期关节炎队列发现冬春季发病者的短期放射学进展更重