单例是应用开发中一种设计模式,主要应用场景为:当且仅当系统中只能保留一个对象时使用。本文提出4中可以在生产环境中使用的单例设计模式。推荐使用enum的方式。
应用场景
例如一下应用场景[1]
:
2、网站的浏览人数统计,一般也是采用单例模式实现,否则难以同步。
3、应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
//todo
在joshua block 的《effective java second edition》 一书中给出了三种单例设计模式1、采用静态变量:
public class TaskManager { public static final TaskManager INSTANCE = new TaskManager (); private TaskManager (){} //...}
这种写法使用了私有的构造方法。来保证只能有一个实例,但是这种方法也有例外情况,因为,你可以通过反射来调用私有构造方法。这个时候你可以抛出异常。以下代码仅作为参考。
public class TaskManager { public static final TaskManager INSTANCE = new TaskManager(); private TaskManager() { if (INSTANCE != null) { try { throw new Exception("An object already exists"); } catch (Exception e) { e.printStackTrace(); } } } //...}
2、采用静态方法
public class TaskManager { private static final TaskManager INSTANCE = new TaskManager(); private TaskManager() {} public static TaskManager getINSTANCE() { return INSTANCE; } //...}
3、采用enum的方式
这种模式是目前最佳的,因为:
1、JVM会保证enum不能被反射并且构造器方法只执行一次。2、此方法无偿提供了序列化机制,绝对防止反序列化时多次实例化。3、运行时(compile-time )创建对象(懒加载) // todo 关于cmpile-time和run-time有时间我单独写一篇文章。enum是jdk5的特性,现在(2017)web应用普遍在jdk6、7、8,所以可以放心使用。
目前最佳的方式是使用接口的方式(解耦):
interface Resource { Object doSomething();}public enum SomeThing implements Resource { INSTANCE { @Override public Object doSomething() { return "I am a Singleton nstance"; } };}class Demo { public static void main(String[] args) { System.out.println(SomeThing.INSTANCE.doSomething()); }}
或者不使用接口的形式
public enum SomeThing { INSTANCE; public void doSomething() { System.out.println("INSTANCE = " + INSTANCE); }}class Demo { public static void main(String[] args) { SomeThing.INSTANCE.doSomething(); }}
也有人用其他的方式,我对这种方法持强烈反对,具体可以参考文献,以下代码仅做参考
class Resource {}public enum SomeThing { INSTANCE; private Resource instance; SomeThing() { instance = new Resource(); } public Resource getInstance() { return instance; }}class Demo { public static void main(String[] args) { System.out.println(SomeThing.INSTANCE.getInstance()); }}
在其他文章中有提到“懒汉”、“恶汉”的名词,其实懒汉主要就是"懒"加载[注:指在使用时装载,不使用时不进行装载]
有人提出这种懒汉设计
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
很显然这种设计线程不安全,一般不会使用。
有人又提出了懒汉改进的方法,使其线程安全。public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,因为是重量级锁,效率很低。
于是有人提出了双重校验锁机制,这个用的也比较多。
下面代码就是用double checked locking 方法实现的单例,这里的getInstance()方法要检查两次,确保是否实例INSTANCE是否为null或者已经实例化了,这也是为什么叫double checked locking 模式。
/*** Singleton pattern example with Double checked Locking*/public class DoubleCheckedLockingSingleton{ private volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton(){} public DoubleCheckedLockingSingleton getInstance(){ if(INSTANCE == null){ synchronized(DoubleCheckedLockingSingleton.class){ //double checking Singleton instance if(INSTANCE == null){ INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; }}
参考文献:
[1] Jason Cai, [2] cantellow, [3] , [4] natsumi, [5] ,