单例模式即代码中只有一个实例的模式
适用场景:有些场景下,有的类只能有一个对象,不能有多个
要注意:在单例模式下,要保证不能产生多个实例
1、饿汉模式
class Singleton{private static Singleton instance = new Singleton();public static Singleton getSingleton() {return instance;}private Singleton(){}
}public class demo {public static void main(String[] args) {Singleton singleton1 = Singleton.getSingleton();Singleton singleton2 = Singleton.getSingleton();System.out.println(singleton1==singleton2);System.out.println(singleton1.equals(singleton2));}
}
将构造方法设为private,使得不能自己初始化类, 只能用默认给定的类
由于是单例,所以singleton1与singleton2地址内容都是一样的,两者是同一个实例
2、 懒汉模式
(1)懒汉模式-不安全
class SingletonLazy{private static SingletonLazy instance = null;public static SingletonLazy getSingletonLazy() {if(instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){}
}public class demo {public static void main(String[] args) {SingletonLazy singletonLazy1 = SingletonLazy.getSingletonLazy();SingletonLazy singletonLazy2 = SingletonLazy.getSingletonLazy();System.out.println(singletonLazy1 == singletonLazy2);System.out.println(singletonLazy1.equals(singletonLazy2));}
}
饿汉模式中,无论原来有没有创建instance,都会初始化一个新的instance
懒汉模式为了节约开销,会在getSingletonLazy()方法中判断instance是否是null。如果不是null,说明instance已经初始化,只须直接返回即可;如果是第一次获取,那么instance没有初始化,是null,这时初始化后再返回
(2)懒汉模式-线程安全
上面的代码存在的问题是:由于判断if(instance == null)这一步和初始化instance = new SingletonLazy()这一步是分开的,有可能会出现线程安全问题
如上图,T1和T2都各创建了一个instance实例,从而出现了两个实例,破坏了单例模式的规则
为了解决上述线程安全问题,我们在getSingletonLazy()方法中对这段代码进行加锁(静态方法代码块加锁对象用类名.class)
public static SingletonLazy getInstance() {synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}return instance;}
(3)空间浪费
改成加锁代码后,产生了一个新问题,那就是:每次获得instance时都要进行加锁,但是加锁本身是一项耗费空间资源的操作,这样便会大大降低代码性能
如果我们能够得知实例已经创建,那么就不用再进行加锁了,所以在加锁之前我们对instance进行一次判断
public static SingletonLazy getInstance() {if(instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;
}
注意,第一次判断和第二次判断作用并不一样:第一次是为了判断是否需要加锁;第二次是为了判断是否需要新创建实例
(4)指令重排序问题
经过两次优化,上述代码仍存在问题,那就是——指令重排序问题
· 什么是指令重排序问题呢
当我们在new一个对象的时候,new这个过程分为三步
1.申请内存空间
2.在内存空间上构造对象 (构造方法)3.把内存的地址,赋值给 instance 引用
这个过程可以按照123来执行,也可以按照132的顺序来执行
在我们上述优化过的代码中,如果T1线程在new的过程中按照132的顺序来执行,那么在执行到第二步时,恰好T2第一次对instance进行判断,由于instance已经创建了实例,那么T2会直接返回这个没有构造对象的instance
如果代码中对返回的instance进行访问属性/方法等操作,程序就会报错
· volatile
为了解决这个问题,我们使用了volatile关键字来对instance变量进行修饰
这样,在进行new操作时,就不会产生指令重排序问题
class SingletonLazy{private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null){synchronized (SingletonLazy.class){if (instance == null){instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){}
}
volatile解决内存可见性问题