我尽量不打错别字,用词准确,不造成阅读障碍。
单例模式是常用的模式之一,对初学者来说也是最好理解的设计模式之一。
应用场合:只要求有且只有一个对象。
作用:保证整个应用中某个实例有且只有一个。
饿汉模式
public class Singleton{
private Singleton(){} //构造方法私有化,禁止外部直接创建对象
private static Singleton singleton = new Singleton();
//用于获取实例方法
public static Singleton getInSingleton(){
return singleton;
}
}
要点:1.构造模式私有化。 2.对外提供获取实例的方法。 这是最简单的饿汉模式,一般不用。“饿汉”是指,对象在调用getInsingleton()方法之前就new好了,很饥饿的样子。
懒汉模式
public class Singleton{
private Singleton(){} //构造方法私有化,不允许外界创建对象
private static Singleton singleton = null;
public static Singleton getInSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
最简单的懒汉模式,“懒汉”指的是在getInSingleton()方法之前对象并没有被创建,而是在getInSingleton()方法中先判断是否为null,否则new对象,比较懒散的样子。
饿汉模式和懒汉模式的区别
饿汉模式:加载类时比较慢,但运行时获取对象的速度比较快,线程安全。
懒汉模式:加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。
懒汉模式改进版(最常用)
如果在多线程的程序中,多个线程同时访问Singleton类,调用getInSingleton()方法,就有可能造成创建多个实例的情况,那么单例模式就失去意义了,所以有了加锁机制。
public class Singleton(){
private Singleton(){}
private static Singleton singleton = null;
public static Singleton getInSingleton(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
此处用的是双重锁定。
第一重null判断:即时加锁避免了多线程访问的问题,但是加锁是耗费资源的,所以并不是每次实例化对象时都加锁,只有对象为null时——即需要创建对象时,进行加锁。
第二重null判断:当对象为null,此时若有两个线程同时访问,则两个线程都可以通过第一重null判断,然后加锁,一个线程创建对象,另一个线程等待,若没有null判断,此时第一个线程创建了对象并释放了锁,而第二个线程还是可以创建对象的,单例的意义又没有了,所以要加判断。
双检锁/双重校验锁模式
采用双锁机制,安全且在多线程情况下能保证高性能。
public class Singleton(){
private Singleton(){}
private volatile static Singleton singleton; //注意volatile关键字
public static Singleton getInSingleton(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
这里使用了volatile关键字,因为多个线程并发时初始化成员变量和对象实例化顺序可能会被打乱,volatile可以禁止指令重排序。双重校验虽然可以在一定程度上解决资源的消耗多余的同步、线程安全的问题,但在某些情况下还是会出现双重校验失效问题,即DCL失效。这个模式我见过的次数不多,而且我不太熟悉,如有问题,请指正。
枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。
public enum Singleton{
INSTANCE;
public void whateverMethod(){
}
}
代码调用:
SingletonEnum.INSTANCE.doSomething();
因为有些模式本人用的不多,甚至没用过,所以有错误欢迎指正。