Java内存模型
由于程序运行中的变量存放在物理内存中,这就引发一个问题,因为cpu在执行程序指令时,速度非常快(远快于内存),如果一直在物理内存中读取或者写入数据,程序的运行效率大大降低,因此诞生了Java内存模型。
Java内存模型表示程序运行的变量保存在物理内存中,每一个线程在执行过程中都拥有一个独立的内存(高效缓存)。运行过程中,先从物理内存中拷贝一份变量到本地内存,进行操作后将变量重新写入到物理内存中。
在多线程环境下,引发另一个问题,缓存一致性问题。比如当有一个共享变量,两个线程分别对其进行自增时,理论值为增加2,但是由于第一个线程在读取变量后并进行自增后,还未写入主存中,第二个线程读取变量也进行自增,之后两个线程都将数据写回主存,这就导致程序执行结束后值只增加1。
为了解决缓存一致性问题,一般有两种方法,基于硬件层面提供的方式:
- 给总线lock上锁
- 实现缓存一致性协议(当变量在某个线程中执行写操作后,通知其他线程的变量为无效,需要重新读取主存中的值)
并发概念
在并发编程中,一般存在三种问题原子性、可见性、有序性
原子性
表示一系列操作要么全部执行,要么全部不执行。
在Java中,对基本数据类型变量的读取写入均为原子操作,比如i=10为原子操作;i++不是原子操作,包括三步,先从主存获取i,然后+1,最后写回主存。
需要注意的是,在32位系统中,对64位数据进行读取写入不是原子操作。为了保证一个大操作的原子性,一般使用synchronized和lock实现。
可见性
表示一个线程对一个共享变量的值做了修改,其他线程立马可以读取到最新的值,volatile则保证了可见性。
有序性
有序性即程序指令执行的顺序与代码的顺序一致,Java程序在运行过程中,有时为了提高效率,允许编译器和处理器对指令进行重排序,但是最终的结果与代码顺序执行的结果一致。
重排序不影响单线程的执行,但是影响多线程执行的正确性,volatile可以保证程序的有序性。
volatile原理
volatile关键字的作用?
- 保证可见性
- 保证有序性,禁止代码的重排序
如何保证可见性和有序性?
volatile编译时加入了lock前缀(内存屏障),当一个共享变量被关键字修饰一个线程对其进行修改时
- 强制将修改的后值立马刷新到主存中
- 通知其他线程变量无效,需要重新读取主存的值
- 内存屏障保证程序的有序性,屏障前后的代码不能调换顺序执行
volatile的使用场景
使用volatile具备两个条件(volatile不能保证原子性):
- 对变量的写不依赖与当前值
- 变量不能存在于其他不变式中
使用场景:
- 标识符
- double check(单例模式)
