先了解一下Java内存模型中的原子性,可见性,有序性:
Java内存模型中的原子性,可见性,有序性
-
原子性指一个操作是按原子的方式执行的。要么该操作不被执行;要么以原子方式执行,即执行过程中不会被其它线程中断。 -
可见性可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。在java中可以实现可见性的两个关键字synchronized,volatile -
有序性如果在本线程内观察,所有的操作都是有序的:如果在一个线程中观察另外一个线程,所有的线程操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性
Synchronized能够实现多线程的原子性(同步)和可见性。
JVM关于Synchronized的两条规定:
-
线程解锁前,必须把共享变量的最新值刷新到主内存中。
-
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要同一把锁)。
Synchronized执行互斥代码的过程
-
获得互斥锁
-
清空工作内存
-
从主内存拷贝变量的最新副本到工作内存
-
执行代码
-
将更改后的共享变量的值刷新到主内存
-
释放互斥锁
volatile可以保证变量的可见性,但是不能保证复合操作的原子性
volatile如何实现内存可见性:
深入来说:通过加入内存屏障和禁止重排序优化来实现的
- 对volatile变量执行
写操作时,会在写操作后加入一条store屏障指令. - 对volatile变量执行
读操作时,会在读操作前加入一条load屏障指令.
通俗地讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,
而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存,
这样任何时刻,不同的线程总能看到该变量的最新值。
线程写volatile变量的过程:
- 改变线程工作内存中volatile变量副本的值
- 将改变后的副本的值从工作内存刷新到主内存
线程读volatile变量的过程:
- 从主内存中读取volatile变量的最新值到线程的工作内存中
- 从工作内存中读取volatile变量的副本
volatile适合的使用场景
只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写入操作不依赖其当前值
- 该变量没有包含在具有其他变量的不变式中。
第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由(读取-修改-写入)操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果只从单个线程写入,那么可以忽略第一个条件。)
总结:
-
volatile比synchronized更轻量级。
-
volatile没有synchronized使用的广泛。
-
volatile不需要加锁,比synchronized更轻量级,不会阻塞线程。
-
从内存可见性角度看,volatile读相当于加锁,volatile写相当于解锁。
-
synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
-
volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。