volatile关键字是JVM提供的一个轻量级的同步机制,可以保证有序性和可见性,不能保证原子性。
volatile原理volatile修饰的变量带有内存屏障(写屏障/读屏障),Mermory Barrier(Mermory Fence)
volatile保证可见性和有序性可见性
volatile修饰的变量是带有写屏障和读屏障的,写屏障之前对于共享变量的修改都刷新到主存中。在读屏障之后,读取的数据都是从主存中加载进来的最新数据,每次读取的时候都从主存读取出来生成一个新的副本。
public class Main { static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { public void run() { // 读屏障,在此之后从主存之中读取共享变量的最新值 while (flag) { } } }); Thread.sleep(2000); t1.start(); Thread t2 = new Thread(new Runnable() { public void run() { flag = false; // 写屏障,在此之前将共享变量的值刷新到主存中 } }); t2.start(); } }
有序性
volatile遵循happened-befores规则:多线程情况下,即便发生了指令重排序也不影响最终的结果。
synchronized&volatile- volatile关键字是线程同步的轻量级实现,性能要比synchronized好
- volatile只能修饰变量;synchronized可以修饰方法和同步代码块
- JDK新版本的发布优化了synchronized,优化后的synchronized执行效率有了很大的提升,一般在开发中使用synchronized
- 多线程反问volatile不会阻塞,synchronized会阻塞
- volatile不能保证原子性,可以保证可见性和有序性;synchronized三个核心问题都可以解决
- volatile解决的是多个线程之间共享数据的可见性;synchronized解决的是多线程之间访问共享资源的同步性
// Double Check Lock public class Singleton { //private static volatile Singleton instance; private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { // new不是原子操作,所以有可能引发多线程问题 instance = new Singleton(); } } } return instance; } public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); } }
- new操作不是原子操作,这一点我们可以通过查看字节码文件来得知
- 查看字节码文件:idea安装jclasslib插件,view->show Bytecode with jclasslib
// 指令含义查看字节码手册 17 new #3 <Singleton> // 1. 在堆区分配内存,生成一个不完全的对象, // 将不完全对象的引入压入栈顶 20 dup // 1. 复制栈顶元素, 将复制数组压入栈顶 21 invokespecial #4 <Singleton.<init> : ()V> // 堆区中的对象就是一个完整的对象了(执行了默认构造方法) 24 putstatic #2 <Singleton.instance : LSingleton;> // 将完整对象的引用赋值给方法区的共享变量
正常流程(上面的流程) // 1.生成一个不完全对象(创建一个对象空间) // 2.初始化对象 // 3.instance指向初始化对象 // 4.单线程访问对象没有问题
如果发生指令重排 // 1.首先创建一个不完全对象(创建一个对象空间) // 2.instance指向这个对象空间 // 3.初始化对象 // 4.单线程访问对象的时候不会影响结果 // 问题:多线程情况下,可能在当前线程在创建对象的过程中, // 由于指令重排序使得其他的线程获取到instance==null,此时instance只对自己的线程可见, // 那么竞争到锁以后,instance依然为null,进而无法保证得到的对象是单例的,此时就需要添加volatile
- Unsafe实现了CAS操作(Java的原子操作类中使用了Unsafe)。
- 在Java代码中,Unsafe类只能通过反射来获取。
- CAS可以将read-modify-write这类的操作转化为原子操作。
- 悲观锁:synchronized可以称为悲观锁,每次只允许一个线程执行同步代码块,其他线程只能阻塞。
- 乐观锁:CAS可以称为乐观锁,允许多个线程同步操作共享资源。
-
CAS+volatile可以实现无锁化编程
- CAS在操作共享变量的时候,如果使用volatile修饰共享变量,可以保证共享变量的可见性。
- 无锁化编程适用于竞争不激烈,多核CPU的情况之下:
- 线程可以并发,不会进入阻塞或者等待状态,可以提高执行的效率;
- 当时当竞争激烈的时候,那么比较的次数就会变多,重试的次数也会变多,CPU占用也就越多,反而影响效率。
- 添加版本号,把数据更新到主存的时候,再次读取主存中的变量的版本号,如果现在变量的版本号与主存中一致则更新。
- 添加时间戳。