定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Singleton类,定义了一个getInstance方法,允许客户访问该方法获取唯一实例,getInstance是一个静态方法,主要负责创建自己及的唯一实例。
单例模式的实现写法
饿汉模式
public class Singleton { //类加载时就会初始化 ,使用了static final进行修饰实例常量,也就是该常量不可改变了而是唯一的 private static final Singleton instance=new Singleton(); //私有构造函数,防止外部调用,确保实例的唯一性 private Singleton() { } //静态方法获取实例 public static Singleton getInstance(){ return instance; } }
用“饿”来形容该模式,感觉很贴切,既然是很饿,肯定很急,需要马上创建实例来缓解饿的程度,不然呵呵。饿汉模式的特点:
主要是通过static final修饰静态实例常量来实现的,所以类加载时就会被初始化实例,基于类加载机制,该实现是线程安全的。同时也是由于类加载的时候已经初始化了实例,在没有调用getInstance方法也会加载到内存中,没有达到懒加载的效果,如果自始至终都没有使用该实例,这样就造成了内存的浪费。
懒汉模式(线程不安全)
public class Singleton { //声明了一个静态对象,初始状态是空对象 private static Singleton instance; //私有构造函数,防止外部调用,确保实例的唯一性 private Singleton() { } //每次调用会先判断对象是否空,若为空则创建(一般是第一次调用被创建),这个方式一定程度节约了资源 //但是也存在缺陷,就是在多线程并行访问时,会创建多个实例,存在线程安全问题 public static Singleton getInstance(){ if (instance==null) { instance=new Singleton2(); } return instance; } }
用“懒”字来形容该模式,是十分贴切的,在需要的时候才会去创建实例,平时不用的情况是不会急着创建实例的,觉得懒人都这样,等需要的才去做,匆匆忙忙的做,结果问题就出来了。懒汉模式的特点:
首先声明了一个空实例的静态对象,在初次调用的时候才会被实例化。达到了懒加载的效果,节约了资源;但缺陷也是存在的,如果多线程并行访问,存在线程安全问题。
我们可以通过例子来验证是非线程安全:
public class Test2 { public static void main(String[] args) { Test2 test2 = new Test2(); new Thread(test2.new Test1Runable()).start(); new Thread(test2.new Test2Runable()).start(); } class Test1Runable implements Runnable{ @Override public void run() { Singleton2 instance = Singleton2.getInstance(); System.out.println("Test1对应的hashCode值:"+instance.hashCode()); } } class Test2Runable implements Runnable{ @Override public void run() { Singleton2 instance = Singleton2.getInstance(); System.out.println("Test2对应的hashCode值:"+instance.hashCode()); } } }
运行结果:
Test1对应的hashCode值:950839416 Test2对应的hashCode值:856722497从结果可以看出,两次的hashCode值是不一样的,很明显是创建了两个实例。
懒汉模式(线程安全)
public class Singleton { //声明了一个静态对象,初始状态是空对象 private static Singleton instance; //私有构造函数,防止外部调用,确保实例的唯一性 private Singleton() { } //基于懒汉模式线程不安全问题进行改进,使用同步的方法进行优化,来保证线程安全 //但同时带来新的问题,每次调用获取实例都要进行同步,这就造成了不必要的同步开销 public static synchronized Singleton getInstance(){ if (instance==null) { instance=new Singleton3(); } return instance; } }同样的可以使用上面main方法进行运行,会发现两者的hashcode值是一样的,使用同步方法可以解决懒汉模式的线程安全问题,但同时带来新问题,就是每次调用获取实例都要进行同步方法,也就是每次获取实例过程中,要先获取锁对象才可以访问实例,这就大大降低反应效率了,带来不必要同步开销,而大多数的情况是不需要用到同步的,一般不建议使用。
双重检查模式(double-checked-locking)DCL
public class Singleton { //声明了一个静态对象,初始状态是空对象 private static Singleton instance; //私有构造函数,防止外部调用,确保实例的唯一性 private Singleton() { } //使用同步代码块,优化懒汉模式的效率问题,但使用双重检查模式,有时候会失效。同样也不十分靠谱 public static Singleton getInstance(){ if (instance==null) { synchronized (Singleton.class) { if (instance==null) { instance=new Singleton(); } } } return instance; } }
在获取实例中,进行两次判null,第一次判断是为了减少不必要的同步,第二次判断是为了初次调用的时候进行创建实例。双重检查模式的优点就是,解决懒汉模式的线程安全、减少不必要的同步方法等问题。但双重检查机制在某些情况会出现失效问题。同样的建议使用。
静态内部类单例
public class Singleton { //私有构造函数,防止外部调用,确保实例的唯一性 private Singleton() { } /** * 定义静态内部类 */ public static class SingletonHolder{ //在静态内部类中使用final修饰初始化实例,保证实例的唯一性,当会调用时才会加载到内存中 private final static Singleton instance=new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; } }
在类加载Singleton时并不会初始化实例instance,而是在调用getInstance方法来实现实例化,原理是第一次调用时会加载内部类SingletonHolder,在内部类定义一个带final static修饰的instance变量(保证实例的唯一性),同时初始化Singleton实例,由java虚拟机来保证线程安全。推荐使用的静态内部类单例模式
枚举单例
public enum Singleton{ INSTANCE; }枚举实现单例的写法十分简单,但可读性不是很高,默认枚举实例是线程安全的,并且任何情况都是单例。
使用场景
在一个系统中,要求一个类仅有一个实例对象。
- 创建一个对象会消耗过多的资源,比如访问数据库和IO操作。
- 一些工具类的对象
小结:单例模式的各种写法,可以理解为都是解决饿汉模式的缺陷而产生的链接方法,然而这些方法在解决了饿汉模式缺陷的同时,也给自己带来潜在的危险,比如说饿汉模式的缺陷是没有达到懒加载的效果,于是懒汉模式站出来了处理懒加载问题,就在此时懒汉模式以为万事大吉了,但是还是出现线程安全问题,于是懒汉模式马上进行自我优化,加入同步方法,解决线程安全问题,通过同步方法问题是解决了,后来还是发现不够理解,每次调用实例都要多出一步操作,必须拿到一把锁才可以打开,效率不是很高也带来资源的消耗;此时苦思冥想得到一个新的方案,就是双重判断,不是说我同步方法耗性能吗,引入同步代码块来实现,最后发现效果还是挺不错的,以后可以关闭问题了,但不好的消息又传来,你那单例也太不稳定了吧,怎么有时候没失效了…………已经无语了……直到静态内部类单例的出现彻底把各种窟窿弥补了,不管实现懒加载的效果,还保证了线程安全问题。最终一统江湖,以后的单例就按我的要求标准写……
感谢:
《大话设计模式》、《Android进阶之光》