原理
可见性
有序性
存在的问题
只能保证本线程之内的代码不重排
双重锁单例模式
存在的问题:
- 可能t1线程在 new对象的时候发生了指令重排序,先给实例赋值之后再创建的
里面的第二步初始化对象包含了:初始化零值,设置对象头,执行init方法
第三步不属于对象创建过程的步骤,第一步加第二步是对象创建的步骤
如果2和3进行了重排序的话,线程B进行判断if(instance==null)时就会为false,而实际上这个instance并没有初始化成功,显而易见对线程B来说之后的操作就会是错得。而用volatile修饰的话就可以禁止2和3操作重排序,从而避免这种情况。volatile包含禁止指令重排序的语义,其具有有序性。
解决办法:
- instance加上volatile修饰,禁止指令重排,保证可见性
并发编程中的三个概念
synchronized: 具有【原子性】,【有序性】和【可见性】
volatile:具有【有序性】和【可见性】
1. 原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
- synchronized具有原子性。synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。因此。
2.有序性
有序性:即程序执行的顺序按照代码的先后顺序执行。
- synchronized具有有序性。synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。因此,synchronized语义就要求线程在访问读写共享变量时只能“串行”执行。
- volatile具有有序性,包含禁止指令重排序的语义。
3.可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- synchronized具有可见性,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。
- volatile具有可见性,通过在指令中添加lock指令,以实现内存可见性。
synchronized 关键字和 volatile 关键字的区别
- volatile 关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。
- volatile 关键字能保证数据的可见性(只能保证是从主存中读的是最新数据,不能保证写的时候没有别的线程在同时写入),但不能保证数据的原子性。synchronized 关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
synchronized
synchronized编译成字节码后,是通过monitorenter(lock原子操作抽象而来)和 monitorexit(unlock原子操作抽象而来)两个指令实现的,具体过程如下:
-
Java 虚拟机会在 MonitorEnter ( 申请锁 )对应的机器码指令之后临界区开始之前的地方插入一个读屏障,这使得读线程加载的是主存中的最新数据
-
Java虚拟机会在 MonitorExit ( 释放锁 )对应的机器码指令之后插入一个写屏障,这就保障了写线程在释放锁之前在临界区中对共享变量所做的更新都同步到主存中去
可以发现,synchronized底层通过读屏障和写屏障的配对使用保证有序性,读屏障和写屏障的配对使用保正可见性。最后又通过锁的排他性保障了原子性与线程安全。
Volatile
经过对比,可以发现 volatile 少了两个指令 monitorenter 与 monitorexit 用来保证原子性与线程安全。