一、概念
java中单例模式是一种常见的设计模式,单例模式分五种:懒汉式单例、饿汉式单例、静态内部类单例、枚举单例和双重校验锁单例。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,回收站、任务管理器、线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免争出多头。
二、五种单例模式的详解
1、懒汉式单例
class LazySingleton{ private static LazySingleton singleton; private LazySingleton(){
} public static LazySingleton getInstance(){ if(singleton==null){ singleton=new LazySingleton(); } return singleton; } }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。
我们可以对懒汉式进行改进,如下所示:
public class Singleton { private static Singleton instance=null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }
在懒汉式的基础上加上了同步锁,使得在多线程的情况下可以用。例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。
缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知,加锁是很耗时的。能避免则避免。
2、饿汉式单例(建议使用)
class HungrySingleton{ private static HungrySingleton singleton=new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return singleton; } }
初试化静态的singleton创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。
缺点:没有lazy loading的效果,从而降低内存的使用率。
3、静态内部类单例
class InternalSingleton{ private static class SingletonHolder{ private final static InternalSingleton INSTANCE=new InternalSingleton(); } private InternalSingleton(){} public static InternalSingleton getInstance(){ return SingletonHolder.INSTANCE; } }
定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
优点:加载时不会初始化静态变量INSTANCE,因为没有主动使用,达到Lazy loading
4.枚举单例
enum EnumSingleton{ INSTANCE; public void doSomeThing(){ } }
《Effective Java》作者推荐使用的方法,优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
五、 双重校验锁单例 ,前后两次判断实例是否存在
class LockSingleton{ private volatile static LockSingleton singleton; private LockSingleton(){} //详见:http://www.ibm.com/developerworks/cn/java/j-dcl.html public static LockSingleton getInstance(){ if(singleton==null){ synchronized(LockSingleton.class){ if(singleton==null){ singleton=new LockSingleton(); } } } return singleton; } }
只有当singleton为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。
缺点:用双重if判断,复杂,容易出错。