目录
并发编程的三个重要特性
1.volatile 的原子性、可见性、有序性
2.volatile 深入解析
3.volatile 和 synchronized
并发编程的三个重要特性
1. 原子性:在一次操作或者多次操作中,要么所有的操作全部得到执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。
2. 可见性:当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。
3. 有序性: 指程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。
1.volatile 的原子性、可见性、有序性
在java中,对基本数据类型读取赋值操作都是原子性的,对引用类型的变量读取和赋值的操作也是原子性的。
PS:1. 多个原子性的操作在一起就不是原子性操作了。
2. 简单的读取和操作都是原子性,将一个变量赋给另外一个变量的操作不是原子性的。
总结:volatile 关键字不具备保证原子性的语义。
Java提供了三种方式来保证可见性:
1. 使用关键字 volatitle ,当一个变量被 volatile 关键字修饰时,对于共享资源的读取操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然是先要修改工作内存,但是修改结束后会立即将其刷新到主内存中。
2. 使用 synchronized 关键字,这个就不多说了
3. 通过JUC提供的显式锁 Lock 。
总结: volatile 关键字具有保证可见性的语义。
有序性同可见性一样,Java 使用了一模一样的这三种方式来保证有序性。
总结: volatile 关键字具有保证顺序性的语义。
2.volatile 深入解析
被 volatile 修饰的实例变量或者类变量具备两层含义:
1. 保证了不同线程之间对共享变量操作时的可见性。也就是当一个线程修改 volatile 修饰的变量,另外一个线程会立即看到最新的值。
2. 禁止对指令进行重排序的操作。
volatile 的变量规则: 对一个变量的写操作要早于对这个变量的读操作。
通过对 OpenJDK 下的 unsafe.cpp 源码的阅读,会发现被 volatile 修饰的变量存在于一个 “lock;” 的前缀。
“lock;” 前缀实际上相当于是一个内存屏障,该内存屏障会为指令的执行提供以下几个保障:
确保指令重排序时不会将其后面的代码排到内存屏障之前。
确保指令重排序时不会将其前面的代码排到内存屏障之后。
确保在执行到内存屏障修饰的指令时前面的代码全部执行完成。
强制将线程工作内存中值得修改刷新到主内存中。
如果是写操作,则会导致其他线程工作内存(CPU Cache)中的缓存数据失效。
volatile 的使用场景
使用 volatile 要充分利用它的可见性和有序性。
但它不能代替 synchronized ,因为 volatile 不具备原子性。
3.volatile 和 synchronized
一、使用上的区别
volatile 只能用于修饰实例变量或者类变量,不能用于修饰方法以及方法参数和局部变量、常量等。
synchronized不能用于对变量的修饰,只能用于修饰方法或者语句块。
volatile 修饰的变量可以为 null ,synchronized同步语句块的 monitor 对象不能 为 null 。
二、对原子性的保证
volatile 无法保证原子性。
由于 synchronized 是一种排他机制。因此被 synchronized 关键字修饰的同步代码是无法被中途打断的,因此能保证代码的原子性。
三、对可见性的保证
两者均可以保证共享资源在多线程间的可见性,但是实现机制完全不同。
synchronized 借助于JVM 指令 monitor enter 和 monitor exit 对通过排他的方式使得同步代码串行化,在 monitor exit 时所有共享资源都将会刷新到主内存中。
相比较于 synchronized 关键字, volatile 使用机器指令(偏硬件)“lock;”的方式迫使其他线程工作内存中的数据失效,不得不到主内存中进行再次加载。
四、对有序性的保证
volatile 关键字禁止 JVM 编译器以及处理器对其进行重排序,所以它能够保证有序性。
synchronized 修饰的同步方法也可以保证有序性,不过是通过程序的串行化执行换来的。
五、其他
volatile 不会使线程陷入阻塞。
synchronized 关键字会使线程进入阻塞状态。
六、volatile 深入解析
被 volatile 修饰的实例变量或者类变量具备两层含义:
1. 保证了不同线程之间对共享变量操作时的可见性。也就是当一个线程修改 volatile 修饰的变量,另外一个线程会立即看到最新的值。
2. 禁止对指令进行重排序的操作。
volatile 的变量规则: 对一个变量的写操作要早于对这个变量的读操作。
通过对 OpenJDK 下的 unsafe.cpp 源码的阅读,会发现被 volatile 修饰的变量存在于一个 “lock;” 的前缀。
“lock;” 前缀实际上相当于是一个内存屏障,该内存屏障会为指令的执行提供以下几个保障:
确保指令重排序时不会将其后面的代码排到内存屏障之前。
确保指令重排序时不会将其前面的代码排到内存屏障之后。
确保在执行到内存屏障修饰的指令时前面的代码全部执行完成。
强制将线程工作内存中值得修改刷新到主内存中。
如果是写操作,则会导致其他线程工作内存(CPU Cache)中的缓存数据失效。
volatile 的使用场景
使用 volatile 要充分利用它的可见性和有序性。
但它不能代替 synchronized ,因为 volatile 不具备原子性。