懒汉模式=懒得生成实例,增加逻辑判断,线程不安全,适合使用频率低,有线程安全问题
饿汉模式=出生自带实例,因为static修饰的变量指向实例对象,牺牲空间,适合于使用频率高
懒汉模式:如果没有1,虽然将synchronized从0移到2缩小了粒度(不在方法上加锁),但是每次线程进来都会加锁进去判断,所以线程多的话会阻塞
于是加上1,去判断有没有必要去加锁,只要对象生成,之后的线程就没有阻塞问题,只有在第一个线程加锁到解锁进来的线程会阻塞
private static Singleton instance;
public static Singleton getInstance()//0 { if (instance == null)//1 { synchronized(Singleton.class) { //2 if (instance == null) //3 instance = new Singleton(); //4 } } return instance; }
但是instance = new Singleton()需要分为三步指令执行
4.1 分配对象内存
4.2初始化对象
4.3引用变量指向对象分配的内存
存在指令重排问题,可能会重排序成以下三步指令
4.1 分配对象内存
4.3引用变量指向对象分配的内存
4.2初始化对象
先执行4.3之后,引用非空(!=null)
在4.3到4.2之间,对象未初始化,其他线程(1这步判断的线程不会加锁,可以理解为另一个线程,与走锁的线程区分开来)进来,判断非空,使用时指向该对象的内存空间发现没有初始化,报错。
虽然加锁保证了抢锁的线程会等待上一个持有锁的线程执行完毕,然后抢到锁之后重新更新instance的值,达到原子性和可见性的目的,但是对于非抢锁线程,指令重排序会将instance的值提前暴露给这些线程
解决方法:使用volatile修饰instance 变量,防止指令重排序
private static volatile Singleton instance;