【设计模式】单例
一、定义
确保每一个类只有一个实例,且类自己实例化这个类
二、使用场景
比如生成唯一的序列号、项目中需要一个共享的资源、spring中的bean默认单例.
三、优缺点
-
优点
1.内存中只有一个实例,减小内存开支,优化和共享资源访问。
-
缺点
1.没有接口,扩展不便(接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的)。
四、代码
1.饿汉式
在初始化时直接创建。
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
优点:线程安全
缺点:消耗资源,不需要实例的时候就生成实例了,我们要的是什么时候需要才生成。
2、懒汉式
在使用时创建对象
public class LazySingleton {
private LazySingleton lazySingleton = null;
private LazySingleton(){};
public LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
存在问题
- 在多现场情况下,如果线程A判断为空,进入了if代码块,还没有new出instance,线程B也判断为null进入了if代码块,则又会new instance并返回。这时候内存中就不唯一实例了。
解决方案
①
在获取实例的方法上加synchronized解决线程不安全问题。
public synchronized LazySingleton2 getInstance() {
if(lazySingleton2 == null){
lazySingleton2 = new LazySingleton2();
}
return lazySingleton2;
}
存在问题
线程问题能够解决,但是我每次去获得实例时,都要在那同步排队获得锁,效率太低。
②使用双重校验实现DCL
public LazySingleton2 getInstance3() {
if(lazySingleton2 == null){
synchronized (LazySingleton2.class){
if(lazySingleton2 == null){
lazySingleton2 = new LazySingleton2();
}
}
}
return lazySingleton2;
}
解析:
DCL方式将同步方法改成了同步代码块,锁的粒度更小,并发更好。
为什么要判断两次instance==null呢?
第一次检测:
由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,每次都要去竞争锁。
第二次检测:
如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。
主要都是防止创建多个实例。
存在问题:因为有指令重排优化性能所以new一个对象大概会走三步。
-
1.分配内存空间:memory = allocate();
-
2.初始化对象:instance(memory)
-
3.设置instance指向刚分配的内存地址.
由于2,3是没有依赖关系的,23顺序不改变执行结果,这种重排序优化是允许的。
指令重排后,当一条线程访问instance不为null时,由于instance未必已经初始化完成,也就造成了线程安全的问题
解决方式:
对instance以volatile修饰。
对于被volatile修饰的变量的写会禁止其重排序。
public class SingletonLazyDCLVolatile {
private static volatile SingletonLazyDCLVolatile instance;
private SingletonLazyDCLVolatile(){}
public static SingletonLazyDCLVolatile getInstance(){
if(instance == null){
synchronized (SingletonLazyDCLVolatile.class){
if(instance == null){
instance = new SingletonLazyDCLVolatile();
}
}
}
return instance;
}
}