并发编程有三个重要特性
1.原子性
所谓原子性是指在一次的操作或者多次的操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。i++不能保证原子性,volatile关键字不保证数据的原子性,synchronized关键字保证
多个原子性操作合在一起就不是原子性操作了
简单的读取和赋值操作是原子性的,将一个变量赋值给另外一个变量的操作不是原子性的
由于synchronized是一种排他机制,因此被他修饰的同步代码是无法被中途打断的,因此其能够保证代码的原子性。
2.可见性
可见性是指当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值
synchronized和Lock保证可见性,他们会在(monitor exit)锁释放之前,会将对变量(共享资源)的修改刷新到主内存中
volatile具有保证可见性的语义
3.有序性
有序性是指程序代码在执行过程中的先后顺序,由于Java在编译器以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序
单利模式中的double-check的写法就是利用了volatile的顺序性
volatile保证了不同线程对共享变量操作时的可见性,也就是说当一个线程修改了volatile修饰的变量,另外一个线程会立即看到最新的值
volatile禁止JVM和处理器对使用volatile修饰的关键字进行指令进行重排序
volatile单例模式
public class Instance {
private static volatile Instance instance;
private Instance() {}
public static Instance getInstance() {
if (instance == null) {
instance = new Instance();
}
}
}
上面代码分成三步原子指令:
1、new指令申请内存;
2、在申请的内存中进行Instance的初始化;
3、将申请的内存地址的引用赋值给instance变量;
虽然volatile可以禁止指令重排序, 让上面三个指令有序执行, 但是问题是volatile并不能保证原子性, 所以上面代码中可能出现的问题是当Thread-A执行到第二步进行new Instance初始化时, 此时还没有将地址值赋给instance变量, 所以Thread-B此时看到的instance==null再次进入if中执行new Instance()操作, 但是为instance赋值堆中的引用是原子操作, 所以此时会进行校验instance是否已经被赋值.
所以假设上面代码被两个线程执行, 那么new Instance()会执行两次, 但是instance赋值操作只会执行一次
网友评论