java并发(四)-原子性、可见性,有序性
4.原子性、可见性,有序性
一、原子性
原子性是指:
一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。
volatile可以保证可见性和有序性,但不能保证原子性。
1. JMM 8个原子操作
在《java并发(三)-JMM内存模型和 volatile缓存一致性》,我们分析了volatile的原理。
其中,JMM内存模型中的8个原子操作,就可以体现 原子性
8个原子操作:
- lock(锁定): 作用于主内存, 将主内存变量 加锁,标识为线程独占状态
- read(读取): 作用于主内存,从主内存读取变量值传送到线程工作内存中
- load(载入): 作用于工作内存, 将read到的数据放入工作内存中的 变量副本 中
- use(使用): 作用于工作内存,将工作内存中的值传递给 执行引擎(计算),每当虚拟机遇到一个需要使用这个变量的指令时,将会执行这个动作
- assign(赋值): 作用于工作内存,将计算好的值重新赋值到工作内存中,每当虚拟机遇到一个需要使用这个变量的指令时,将会执行这个动作
- store(存储): 作用于工作内存 ,将工作内存数据传送给主内存中,以备随后的write操作使用
- write(写入): 作用于主内存, 将store传递值赋值给主内存中的变量
- unlock(解锁): 作用于主内存 ,将主内存变量解锁,解锁后其他线程可以锁定该变量,被其他线程锁定。
2. synchronized满足原子性
- 由原子性变量操作 read,load,use,assign,store,write,可以大致认为基本数据类型的访问读写具备原子性(例外就是long和double的非原子性协定)
- 另外两条原子操作,lock和unlock,jvm并没有开放给我们,但jvm以更高层级的指令:monitorenter和monitorexit 指令 开放给我们使用,反映到java代码就是 synchronized关键字。
- 也就是说:synchronized 满足原子性。
3. volatile不满足原子性
我们通过代码来举例说明:
public class AtomicTest {
private static volatile int num = 0;//volatile修饰
public static void increase(){
num++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for(int i = 0; i < threads.length; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 1000; i++){
increase();
}
}
});
threads[i].start();
}
for(Thread t: threads){
t.join();//线程执行了join(),表示主线程得等待所有调用了join() 线程执行完。
}
System.out.println(num);
}
}
输出结果:
总是小于等于 10000
分析原因:
为什么加了 volatile 修饰的num不能满足原子性:

分析:
- 一开始,num = 0;
- 可能存在这样的情况,线程1和线程2同时读取了num = 0 的值到各自的工作内存中
- 线程1(右边的线程)经过read、load、use、assign、store、write操作后,回写主内存,num = 1。
- 线程2(左边的线程)虽然num已经等于1了,但由于缓存一致性,cpu嗅探总线发现num值变化,会让线程1的num值失效,需要线程1重新读取主内存最新的num值。
- 这时读到的num=1。在经过一系列操作后,num = 2。
- 我们可以发现,经过了三次num++ 后,num = 2。
- 可见,volatile不能保证原子性。
保证原子性:
可以通过 AtomicInteger 类保证原子性。以后再讲
二、 可见性
可见性: 是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。
在《JMM内存模型与volatile缓存一致性》中,我们分析了volatile底层原理,可以看到volatile是具有可见性的。
对于synchronized而言,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。从而,synchronized具有可见性
三、 有序性
有序性: 顾名思义,就是程序按照代码的先后顺序执行。
1. volatile 具有有序性
代码分析:
public class SerialTest {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (;;){
i++;
x = 0; y = 0;
a = 0; b = 0;
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
String result = "第" + i + "次(" + x + "," + y + ")";
if(x==0 && y==0){
System.err.println(result);
break; //(0,0) 停止循环
}else {
System.out.println(result);
}
}
}
}
输出结果:(有四种可能的结果)
(1, 0)
(0, 1)
(1, 1)
(0, 0)
如果不发生指令重排,那么只有前面三种情况存在。而因为发生了指令重排,我们可以看到程序停止,输出了 (0, 0) 的情况。
依照 之前提到的 重排序, 我们知道,在单线程内,如果没有数据依赖性,是可以重排序的。
所以当 x = b 重排序到 a = 1 前,以及 y = a 重排序到 b = 1前面,所以出现了(0, 0) 的情况。
显然这种重排序在 单线程 不影响结果,
在线程 t1 内部: a = 1; x = b; 是互不影响的;同样的,
在线程 other内部: b = 1; y = a;是互不影响的。
但在多线程中,会影响我们预想的结果。所以这个程序是不具有有序性的。
用volatile关键字修饰:
我们用volatile关键字修饰 变量 a 和变量 b:
private volatile static int a = 0, b = 0;
结果可以发现,并没有 (0,0) 的情况,程序也不会停止。
这说明程序并没有发生指令重排,所以 volatile是具有有序性的。
2. synchronized具有有序性
synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。因此,synchronized语义就要求线程在访问读写共享变量时只能“串行”执行,因此synchronized具有有序性。
3. volatile 结合synchronized分析
在java内存模型中说过,为了性能优化,编译器和处理器会进行指令重排序;也就是说java程序天然的有序性可以总结为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。在单例模式的实现上有一种双重检验锁定的方式(Double-checked Locking)。代码如下:
public class Singleton {
private Singleton() { }
private volatile static Singleton instance;
public Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
分析不加volatile修饰的情况:
instance = new Singleton();
重点分析这句代码
这条语句实际上包含了三个操作:
- 分配对象的内存空间;
- 初始化对象;
- 设置instance指向刚分配的内存地址。
但由于存在重排序的问题,可能有以下的执行顺序:

如果2和3进行了重排序的话,线程B进行判断if(instance==null)时就会为true,而实际上这个instance并没有初始化成功,显而易见对线程B来说之后的操作就会是错得。
而用volatile修饰的话就可以禁止2和3操作重排序,从而避免这种情况。volatile包含禁止指令重排序的语义,其具有有序性。
volatile怎么做到禁止重排序,从而实现有序性的呢?
就是使用了 内存屏障。
总结一下
synchronized: 具有原子性,有序性和可见性;
volatile:具有有序性和可见性

浙公网安备 33010602011771号