单例模式-线程安全的写法

228 阅读3分钟

单例的定义

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。即,在整个系统运行过程中,只允许产生一个实例(有且只有一个)。

饿汉式

在类加载的时候就立即初始化,并且创建单例对象

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好;绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题

缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间

public class Hungry {
    private Hungry(){}
    private static final Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return  hungry;
    }
}

懒汉式

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

public class LazySingleton(){
	private LazySingleton(){}
    private static LazySingleton lazy = null;
    
    public static synchronized LazySingleton getInstance(){
    	if(lazy == null){
        	lazy = new LazySingleton();
        }
        return lazy;
    }
}

枚举式

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。

public enum EnumSingleton{
	INSTANCE;
    public void whateverMethod(){
    }
}

静态内部类

利用了 classloader 机制来保证初始化 instance 时只有一个线程,延迟加载。在外部类被调用的时候内部类才会被加载,内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题.这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题,完美地屏蔽了这两个缺点.

public class Singleton{
	private Singleton(){}
    public static final Singleton getInstance(){
    	return SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder{
    	private static final Singleton INSTANCE = new Singleton();
    }
}

双检锁/双重校验锁(DCL,即 double-checked locking)

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class DoubleSingleton{
	private volatile static DoubleSingleton instance;
    private DoubleSingleton(){}
    public static DoubleSingleton getInstance(){
    	if(null == instance){
        	synchronized(DoubleSingle.class){
            	if(null == instance){
                	instance = new DoubleSingleton();
                }
            }
        }
        return instance;
    }
}

如何防止反序列化破坏单例

public class SerializableSingleton implements Serializable {

	public  final static Seriable INSTANCE = new Seriable();
    
    private Seriable(){}

    public static  Seriable getInstance(){
        return INSTANCE;
    }
	// 该方法保证反序列化时返回的是同一个实例
    private  Object readResolve(){
        return  INSTANCE;
    }
}

防止利用反射,获取私有构造函数二次创建实例

// 在构造函数内判断单例是否已存在,存在则抛出异常,防止反序列化破坏单例
private Singleton2(){
    if(instance !=null){
        throw new RuntimeException("正在实例化对象");
    }
}