单例模式:
对于某些应用场景下,某应用在整个系统运行期间,无需创建多份,典型应用如任务管理器,文档编辑工具(Office)。一是不会造成系统资源浪费,二不会出现内容不一致的情况。这样通过单例模式可以保证一致性。
首先单例模式需要保证不能通过new关键字创建对象实例,及控制构造函数私有,然后通过统一的静态方法入口获取单一实例。
单例模式常见的有两种:恶汉式、懒汉式。先看一下饿汉式模式
1、饿汉模式
饿汉模式,即在类加载时就创建对象,代码如下
class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instanct;}
2、懒汉模式
懒汉模式,即实现“”延迟加载”,对象实例声明为null ,当调用时进行new。代码如下
class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
但是懒汉有一个问题,即不能保证线程安全,考虑到多线程并发访问的时候可能导致对象不一致,为此可以为getInstance()方法前添加synchronized关键字,由于锁力度较大,可以考虑在instance = new LazySingleton();前增加synchronized代码块控制。另外这种情况也是会出现不一致的情况,通常类初始化较为复杂,可能两个线程都进入instance为null的判断,因此为保证一致性可以考虑在synchronized代码块中增加第二重为null判断,即双重锁检查(Double Lock Check)。
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
注意:instance声明处,需要将其加上volatile关键字,可以确定多个线程可以正确处理。
3、饿汉与懒汉模式的优缺点
饿汉式单例在类初始化加载时,即创建。无需考虑线程安全,可以确保实例的一致性。调用速度,反应时间要较懒汉式优秀。资源利用率上,类加载时都需要进行实例化对象,时间可能更长。
懒汉式单例,第一次使用时占用系统资源,实现延迟加载,必须处理好多线程的访问安全性问题,可通过双重检查锁进行控制。
有没有既能保证一致性,也可以提交访问效率、提高资源利用率的方式呢?
4、Initialization Demand Holder (IoDH)
//Initialization on Demand Holder class Singleton { private Singleton() { } private static class HolderClass { private final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; } public static void main(String args[]) { Singleton s1, s2; s1 = Singleton.getInstance(); s2 = Singleton.getInstance(); System.out.println(s1==s2); } }
由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能。
(参考http://gof.quanke.name/)