线程安全之可见性问题

线程安全之可见性问题

1 Java内存模型

Java内存模型(Java Memory Model,简称JMM。PS:注意与Java运行时数据区的区别),是针对Java在多线程并发下可能出现的各种问题而提出的一种规范。主要围绕:可见性原子性有序性来展开陈述。要阐述清楚JMM,我们需要从最底层的内存屏障、同步机制的内存语义,再到最上层的happen-before规则,逐步解析JMM的前世今生。希望能通过这篇文章,让自己对JMM有一个比较清晰的理解。

能够被多个线程访问的共享变量是内存模型规范的对象。

两个关键性问题

JMM主要是解决两个问题:线程间如何通信、线程间如何同步

Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行。

**线程间如何通信?**方式一般有两种:

1. 共享内存,通过读-写公共状态来隐式通信。
2. 消息传递,没有公共状态,只能通过收发消息来显式通信。

线程间如何同步?

同步是指线程中用于控制不同线程间操作发生相对顺序的机制。

1. 共享内存,同步必须显式指定某个方法或某段代码在线程间互斥执行。
2. 消息传递,发送必须在接受之前,所以同步是隐式进行的。

Java并发采用的是共享内存的方式。

所有线程间操作,都存在可见性问题,JMM需要对其进行规范。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tXrzZR4-1578474614874)(/Users/lucky/Library/Application%20Support/typora-user-images/image-20200107143028003.png)]

2 JMM的抽象结构

​ Java线程间通信由JMM控制,JMM决定一个线程对共享变量的修改何时对另一个线程可见。
Java为了提高程序执行效率,将内存分为两种:主内存和本地内存。

  1. 主内存,存放着共享变量。
  2. 本地内存,存放着共享变量的副本或拷贝,本地内存并不真实存在,而是JMM的一个抽象概念。

下图为两者之间的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhpWDg9d-1578474527606)(/Users/lucky/Library/Application Support/typora-user-images/image-20200108100152800.png)]

3 指令重排

​ Java编程语言的语义允许Java编译器(JIT编译)和微处理器(CPU)进行执行优化,这些优化导致了与其交互的代码不再同步,从而导致看似矛盾的行为。

​ 指令重排只保证单个线程的一致性。指令重排之后,可能影响到其他的线程执行结果。

4 JIT编译器(Just In Time Compiler)

脚本语言 与 编译语言的区别?

​ 解释执行:即咱们说的脚本,在执行时,由语言的解释器将其一条条翻译成机器可识别的指令。

​ 编译执行:将我们编写的程序,直接编译成机器可以识别的指令码。可以批量编译。

​ Java属于解释执行,当方法被频繁调用的时候,或者是方法体多次循环的时候就能升级成编译执行(JIT编译)。热点代码

5 volatile关键字

【注】:volatile具有原子性可见性的特性,但volatile修饰的变量和其他操作组合时并不具有原子性,例如自增操作。

可见性问题:让一个线程对共享变量的修改,能够及时的被其他的线程看到。

​ 所有的实例字段、静态字段和数组元素都存储在堆内存中,这些字段和数组都属于共享变量。

​ 冲突:如果至少有一个访问是写操作,那么对同一个变量的两次访问时冲突的。

多线程操作存在可见性问题,volatile关键字解决多线程的可见性问题。

正如:

Java内存模型规定:

​ 对volatile变量v的写入,与所有其他线程后续对v的读同步。

要满足这些条件,所以volatile关键字就有这些功能:

​ 1 禁止缓存:volatile变量的访问控制符会加个ACC_VOLATILE

​ 2 对volatile变量相关的指令不做重排序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5cnXBQT-1578474527607)(/Users/lucky/Library/Application Support/typora-user-images/image-20200107141541621.png)]

volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存

volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应本地内存置为无效,接下来将从主内存中读取共享变量

6 final

final变量的重排序规则

  1. 写final变量重排序。在构造方法内对一个final变量的写入,与随后把这个构造方法所属的对象的引用赋值给另一个引用变量,这两个操作之间不能重排序
  2. 读final变量重排序。初次读一个包含final变量的对象的引用,与随后初次读这个final变量,这两个操作之间不能重排序

总结

  1. JMM抽象化了硬件的内存模型,屏蔽了CPU和操作系统的差异性

  2. JMM最根本解决的问题是主内存和本地内存的操作顺序

  3. JMM围绕着原子性、可见性和有序性来展开并设置规范的

  4. synchronized实现了这三种特性,volatile只实现了可见性和有序性,final也能实现可见性

  5. happen-before规则定义了volatile和锁在使用的时候哪些不能重排序

【注】最重要的还是,JMM是并发的基础,不了解JMM,就不可能高效地并发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值