概念
一个类有且仅有一个实例,并自行实例化向整个系统提供这个实例。
使用场景
确保某个类有且仅有一个类的实例(对象),避免产生过多的实例消耗资源,或者某些需求中,只需一个对象的情况。譬如,
IO操作或者数据库连接类的相关对象,对性能消耗过大地操作,或者频繁地创建和销毁的对象,就需要考虑是否使用该模式。
特点
注意:getInstance()方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance被多次实例化。
优点
缺点
简单示例
public class SingletonClass {
// 创建SingletonClass实例
private static SingletonClass singleInstance = new SingletonClass();
// 私有的构造函数,防止通过构造函数实例化对象
private SingletonClass(){}
// 获取实例
public static SingletonClass getInstance() {
return singleInstance;
}
}
调用方式
public class SingletonPatternTest {
public static void main(String[] args) {
//获取唯一可用的对象
SingletonClass singletonObj = SingletonClass.getInstance();
}
}
其他实现方式
懒汉模式一
懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化。这种方式是最基本的实现方式,由于没有加同步锁 synchronized,所以它不支持多线程。严格来讲,算不上单例模式。
// 懒汉模式一,不要求线程安全,不适用于多线程中
public class SingletonClass {
private static SingletonClass singleInstance;
private SingletonClass(){}
public static SingletonClass getInstance() {
if (null == singleInstance) {
singleInstance = new SingletonClass();
}
return singleInstance;
}
}
懒汉模式二
此方式添加了同步锁 synchronized,所以它能够支持多线程。但是,效率很低,99% 情况下不需要同步。
// 懒汉模式二,要求线程安全,适用于多线程中
public class SingletonClass {
private static SingletonClass singleInstance;
private SingletonClass(){}
public static synchronized SingletonClass getInstance() {
if (null == singleInstance) {
singleInstance = new SingletonClass();
}
return singleInstance;
}
}
总结:懒汉模式的优点是单例只有在使用时才被实例化,在一定程度上节约了资源开销;缺点是第一次加载时需要即时进行实例化,反应稍慢,
而且每次调用getInstance都进行同步,造成不必要的同步开销,这种模式不推荐使用。
饿汉模式
此模式基于classloader机制避免了多线程的同步问题,但是,instance在加载的时候就已经实例化了,导致类装载的方式有很多种,在单例模式中,大多数都是调用getInstance方法导致的,当然也不排除其他方式(或者其他静态方法)导致类装载。这时候初始化显然是没有达到懒加载的效果。
public class SingletonClass {
private static SingletonClass singleInstance = new SingletonClass();
private SingletonClass(){}
public static SingletonClass getInstance() {
return singleInstance;
}
}
总结:
优点:没有同步锁,执行效率高;
缺点:类加载时就初始化,浪费内存资源;
Double Checke Lock(DCL)双重校验锁/双检锁模式
此模式采用双锁机制,安全且在多线程情况下能保持高性能。既能在需要的时才初始化单例,又能保证线程安全,且对象实例化后调用getInstance不进行同步锁。getInstance()的性能对应用程序很关键。
public class SingletonClass {
private static volatile SingletonClass singleInstance = null;
private SingletonClass(){}
public static SingletonClass getInstance() {
if (null == singleInstance) {// 避免不必要的同步
synchronized (SingletonClass.class) {
if (null == singleInstance) {// 在未实例化的情况下才创建实例
singleInstance = new SingletonClass();
}
}
}
return singleInstance;
}
}
总结:
DCL优点:资源利用率高,第一次执行getInstance时单例对象才会被初始化,效率高。
DCL缺点:第一次加载反应稍慢,也由于Java内存模型的原因偶尔会失败(Java编译器允许处理器乱序执行程序指令,JDK1.6后修复),
在高并发情况下也有一定的缺陷,但是发生频率很小。
此模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景中保证单例对象的唯一性,
除非你的代码在并发场景比较复杂或低于JDK6版本下使用,否则,此方式一般能够满足需求。
静态内部类模式
静态内部类模式主要用于弥补DCL模式的缺陷(即DCL失效)。
public class SingletonClass {
private SingletonClass(){}
public static SingletonClass getInstance() {
return SingletonHolder.singleInstance;
}
/** 静态内部类 */
private static class SingletonHolder {
private static final SingletonClass singleInstance = new SingletonClass();
}
}
总结:
静态内部类模式,首次加载SingletonClass类时并不会初始化singleInstance,
只有在第一次的调用getInstance()方法时才会加载SingletonHolder类,这种方式不仅能够确保线程安全,
也能够保证单例对象的唯一性,同时也延迟了单例的实例化。推荐使用的单例模式。
枚举模式
枚举模式是最简单的单例实现方式,也是最佳的单例实现方式。简洁,自动支持序列化机制,绝对防止多次实例化。由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
public enum SingletonClass {
INSTANCE;
public void doSth() {
// TO DO
}
}
总结:枚举单例模式的最大优点就是写法非常简单。枚举在Java中与普通的类一样,不仅能够有字段,还能够有自己的方法,
最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下都是一个单例。
容器模式
容器模式,是将不同的单例对象放在Map中,通过key获取对象对应的类型的对象。代码如下:
public class SingletonClass {
private static Map map = new HashMap();
private SingletonClass() {}
public static void registerService(String key, Object instance) {
if (!map.containsKey(key)) {
map.put(key, instance);
}
}
public static Object getService(String key) {
return map.get(key);
}
}
总结:容器模式可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低使用成本,隐藏了具体实现,
降低了耦合度。
正常情况下,建议使用【饿汉模式】,如果要明确实现懒加载效果,可使用【静态内部类模式】,如果涉及反序列化创建对象时,建议采用【枚举模式】,如果想管理多种类型的单例,可以考虑【容器模式】,特殊需求,可以试试【双检锁模式】。
注:本帖子相关内容参考于网络,侵删。