单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
在开发过程中,经常遇到一种情况。有一个类,我希望它在系统中只有一个实例,并且易于访问。就像很久以前曾经写过一个仓库管理系统,在点击新建仓库的时候会弹出仓库信息录入的窗口,再次点击会再次弹出一个窗口,如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源。那么如何保证这个类只有一个实例并且易于访问呢?一个解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。
单例模式
单例模式结构图

下面我们定义一个Singleton类,在定义一个getInstance方法,该方法是一个静态方法,其作用是创建自己的唯一实例并提供访问。
public class Singleton{ private static Singleton instance=null; public static Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } private Singleton(){} }
测试代码
public static void main(String[] args) { //比较单例模式生成对象是否相等 Singleton a = Singleton.getInstance(); Singleton b = Singleton.getInstance(); System.out.println(a==b); }
输出结果为:true,通过输出结果可以看出,该对象已经实现了对自身唯一实例的创建并提供访问。
多线程时的单例模式(双重锁定)
在多线程程序中,多个线程同时访问Singleton类,调用getInstance()方法,会有可能造成多个实例的创建。这个时候该怎么处理呢。这个时候就用到了双重锁定,下面的代码即展示了双重锁定。
public class Singleton{ private static Singleton instance=null; private Singleton(){} public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(null==instance){ instance=new Singleton(); } } } return instance; } }
这样做,就可以实现多线程程序的单例模式,并且不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全,这种方法就被成为双重锁定(Double-Check Locking)。
单例模式通常的三种形式
懒汉式
package com.yimik.test.singleton; /** * 单例模式-懒汉式,也是常用的形式。 */ public class Singleton1{ private static Singleton1 instance=null; public static Singleton1 getInstance(){ if(instance==null){ instance=new Singleton1(); } return instance; } private Singleton1(){ System.out.println(this.getClass().getName()+"实例化..."); } }
饿汉式
package com.yimik.test.singleton; /** * 单例模式-饿汉式 */ public class Singleton2{ private static final Singleton2 instance = new Singleton2(); private Singleton2(){ System.out.println(this.getClass().getName()+"实例化..."); } public static Singleton2 getInstance(){ return instance; } }
饿汉式在正常情况下已经可以满足几乎所有需求。
双重锁的形式
package com.yimik.test.singleton; /** * 单例模式-双重锁的形式,这个模式将同步内容下方到if内部, * 提高了执行的效率,不必每次获取对象时都进行同步, * 只有第一次才同步,创建了以后就没必要了。 */ public class Singleton3{ private static Singleton3 instance=null; private Singleton3(){ System.out.println(this.getClass().getName()+"实例化..."); } public static Singleton3 getInstance(){ if(instance==null){ synchronized(Singleton3.class){ if(null==instance){ instance=new Singleton3(); } } } return instance; } }
最后是测试代码
package com.yimik.test.singleton; public class TestSingleton { public static void main(String[] args) { //比较单例模式生成对象是否相等 Singleton2 a = Singleton2.getInstance(); Singleton2 b = Singleton2.getInstance(); System.out.println(a==b); //多线程环境下 Thread t1 = new Thread(){ @Override public void run() { Singleton1.getInstance(); Singleton3.getInstance(); } }; Thread t2 = new Thread(){ @Override public void run() { Singleton1.getInstance(); Singleton3.getInstance(); } }; t1.start(); t2.start(); } }
最终的输出结果如下:
com.yimik.test.singleton.Singleton2实例化... true com.yimik.test.singleton.Singleton1实例化... com.yimik.test.singleton.Singleton1实例化... com.yimik.test.singleton.Singleton3实例化...
由输出结果可得知,在多线程环境下,懒汉式的Singleton1类被实例化了多次,而采用双重锁定模式的Singleton3类,只被实例化了一次,实现了多线程环境下的单例模式。