设计模式之单例模式

81 阅读2分钟

单例模式是指确保一个类在任何情况下都绝对只有一个实例

饿汉式单例模式

饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象,它绝对线程安全

优点:没有加锁,执行效率比较高

缺点:不管需不需要用到,都会加载该实例,用于单例对象比较少的情景

public class HungrySingleton {
	private static final HungrySingletion hungrySingleton = new HungrySingleton();
    
    private HungrySingleton() {}
    
    public static HungrySingleton getInstance() {
    	return hungrySingleton;
    }
    
}

懒汉式单例模式

懒汉式单例模式的特点是只有在外部调用时才会开始加载该实例

简单实现

public class LazySimpleSingleton {
	private static LazySimpleSingleton lazySimpleSingleton = null;
    
    priavte LaztSimpleSingletom() {}
    
    public static LazySimpleSingleton getInstance() {
    	if(lazySimpleSingleton == null) 
        	lazySimpleSingleton = new LazySimpleSingleton();
       return lazySimpleSingleton;
    }
    
}

这样的实现存在线程安全问题

DCL(双重检验锁的实现)

public class LazyDoubleCheckSingleton {
	// volatile 禁止指令重排
    // 对象的初始化可以简单地分为3步
    // 1. 申请内存
    // 2. 对象初始化
    // 3. 将引用与对象相关联
    // 在cpu执行过程中可能出现这样的执行顺序:1-3-2
    // 如果在一个线程调用getInstance方式,刚好以这样的顺序执行那么另一个线程就有可能获取到未初始化的对象
	public volatile static LazyDoubleCheckSingleton lazy = null;
    
    private LazyDoubleCheckSingleton() {}
    
    public static Lazy LazyDoubleCheckSingleton getInstance() {
    	// 在实例为null时,才进行加锁,提高效率
    	if(lazy == null) {
        	synchronized (LazyDoubleCheckSingle.class) {
            	// 再次判断是因为可能同时出现两个或者多个线程争夺锁,没有获得锁的线程会暂时阻塞
                // 当获取到锁的那个线程释放锁的时候,必然将对象new完了,如果不加上这个判断,则其他获取到锁的线程就会重新new一个新的对象出来
            	if(lazy == null) {
                	lazy = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazy;
    }
}

静态内部类实现

兼顾了饿汉式单例的内存浪费问题和synchronized的性能问题

public class LazyInnerClassSingleton {
	
    private LazyInnerClassSingleton() {}
    
    public static final LazyInnerClassSingleton() {
    	return LazyHolder.lazy;
    }
    
    private static class LazyHolder {
    	private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
    }
    
}

反射破坏单例

public class LazyInnerClassSingletonTest {
	public static void main(String[] args) {
    	try {
        	// 获取类对象
            Class<?> clazz = LazyInnerClassSingleton.class;
            // 获取私有构造方式
            Constructor c = clazz.getDeclaredConstructor();
            // 强制访问
            c.setAccessible(true);
            // 调用构造方式 生成两个对象
            Object o1 = c.newInstance();
            Object o2 = c.newInstance();
        } catch(Exception e) {
        	e.printStackTrace();
        }
        
    }
}

序列化破坏单例

通过对象序列化也可以破坏单例,如果增加readResolve方法则可以防止

枚举式单例模式

使用枚举类单例可以解决序列化破坏单例的问题,同时枚举类不允许通过反射创建

public enum EnumSington {
	INSTANCE;
    private Object data;
    public static EnumSingleton getInstance() {
    	return INSTANCE;
    }
}

容器类单例

使用Map将类与对象一一对应,适用于实例多的情况,但是是非线程安全的