我在项目当中,用的最多的是C++,不是Java,但是在做C++时,有很多的设计理念借鉴了Java,所以对Java也有一些研究。
这里转载的一个Java单例模式的文章,研究还是很透彻的。
转自:http://www.ibm.com/developerworks/cn/java/j-lo-Singleton/#ibm-pcon
从专业化来说,单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。Java 里面实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的 ClassLoad 装载实现单例类的时候就会创建一个类的实例。在 Java 语言中,这样的行为能带来两大好处:
-
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
-
由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
因此对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效地改善系统的性能。单例模式的核心在于通过一个接口返回唯一的对象实例。首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类的实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法,代码如清单 1 所示:
清单 1. 单例模式基本实现
public class Singleton { private Singleton(){ System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInsatnce(){ return instance; } }
首先单例类必须要有一个 private 访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化,;其次,instance 成员变量和 getInstance 方法必须是 static 的。
上述代码唯一的不足是无法对 instance 实例做延时加载,例如单例的创建过程很慢,而由于 instance 成员变量是 static 定义的,因此在 JVM 加载单例类时,单例对象就会被建立,如果此时这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。代码如清单 2 所示:
清单 2. 单例模式实验
public class Singleton { private Singleton(){ System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInsatnce(){ return instance; } public static void createString(){ System.out.println("createString in Singleton"); } public static void main(String[] args){ Singleton.createString(); } }
上述代码运行后的输出如清单 3 所示:
清单 3. 清单 2 代码运行运行后输出
Singleton is create createString in Singleton
可以看到,虽然此时并没有使用单例类,但它还是被创建出来,为了解决这类问题,需要引入延迟加载机制,代码如清单 4 所示:
清单 4. 延迟加载的单例模式代码
public class LazySingleton { private LazySingleton(){ System.out.println("LazySingleton is create"); } private static LazySingleton instance = null; public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } public static void createString(){ System.out.println("create String"); } public static void main(String[] args){ LazySingleton.createString(); } }
上述代码运行后的输出如清单 5 所示:
清单 5. 清单 4 代码运行后输出
create String
清单 4 所示代码首先对于静态成员变量 instance 初始化赋值 null,确保系统启动时没有额外的负载;其次,在 getInstance() 工厂方法中,判断当前单例是否已经存在,若存在则返回,不存在则再建立单例。这里尤其要注意的是,getInstance() 方法必须是同步的,否则在多线程环境下,当线程 1 正新建单例时,完成赋值操作前,线程 2 可能判断 instance 为 null,故线程 2 也将启动新建单例的程序,而导致多个实例被创建,故同步关键字是必须的。由于引入了同步关键字,导致多线程环境下耗时明显增加。两者测试代码如清单 6 所示:
清单 6. 非同步的单例模式代码
public class Singleton implements Runnable{ private Singleton(){ System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInsatnce(){ return instance; } public static void createString(){ System.out.println("createString in Singleton"); } @Override public void run() { // TODO Auto-generated method stub long beginTime = System.currentTimeMillis(); for(int i=0;i<10000;i++){ Singleton.getInsatnce(); } System.out.println(System.currentTimeMillis() - beginTime); } public static void main(String[] args){ //Singleton.createString(); for(int i=0;i<5;i++){ new Thread(new Singleton()).start(); } } }
清单 6 所示代码运行的输出如清单 7 所示:
清单 7. 清单 6 代码运行后输出
Singleton is create Singleton is create Singleton is create 0 Singleton is create Singleton is create 0 0 Singleton is create 0 0
清单 8. 完整的延迟加载方式代码
public class LazySingleton implements Runnable{ private LazySingleton(){ System.out.println("LazySingleton is create"); } private static LazySingleton instance = null; public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } public static void createString(){ System.out.println("create String"); } @Override public void run() { // TODO Auto-generated method stub long beginTime = System.currentTimeMillis(); for(int i=0;i<1000000;i++){ LazySingleton.getInstance(); } System.out.println(System.currentTimeMillis() - beginTime); } public static void main(String[] args){ //LazySingleton.createString(); for(int i=0;i<5;i++){ new Thread(new LazySingleton()).start(); } } }
清单 9. 清单 8 代码运行后输出
LazySingleton is create LazySingleton is create LazySingleton is create LazySingleton is create LazySingleton is create LazySingleton is create 1139 1202 1234 1218 1234
为了解决同步关键字降低系统性能的缺陷,做了一定改进,代码如清单 10 所示:
清单 10. 解决同步关键字低效率
public class StaticSingleton { private StaticSingleton(){ System.out.println("StaticSingleton is create"); } private static class SingletonHolder{ private static StaticSingleton instance = new StaticSingleton(); } public static StaticSingleton getInstance(){ return SingletonHolder.instance; } }
清单 10 的单例模式使用内部类来维护单例的实例,当 StaticSingleton 被加载时,其内部类并不会被初始化,故可以确保当 StaticSingleton 类被载入 JVM 时,不会初始化单例类,而当 getInstance() 方法调用时,才会加载 SingletonHolder,从而初始化 instance。同时,由于实例的建立是时在类加载时完成,故天生对多线程友好,getInstance() 方法也无需使用同步关键字。
单例模式的本质
单例模式是为了控制在运行期间,某些类的实例数目只能有一个。如果你想要控制多个,可以利用 Map 来帮助缓存多个实例,代码如清单 11 所示:
清单 11. 控制 3 个实例
import java.util.HashMap; import java.util.Map; /** * 扩展单例模式,控制实际产生实例数目为 3 个 * @author zhoumingyao * */ public class ThreeSingleton { private final static String DEFAULT_PREKEY = "cache";//为后面使用的 key 定义一个前缀 private static Map<String,ThreeSingleton> map = new HashMap<String,ThreeSingleton>();//定义缓存实例的容器 private static int number = 1;//定义初始化实例数目为 1 private final static int NUM_MAX = 3; private ThreeSingleton(){ } public static synchronized ThreeSingleton getInstance(){ //通过缓存理念及方式控制数量 String key = DEFAULT_PREKEY + number; ThreeSingleton threeSingleton = map.get(key); if(threeSingleton==null){ threeSingleton = new ThreeSingleton(); map.put(key, threeSingleton); } number++;//实例数目加 1 if(number>NUM_MAX){ number=1; } return threeSingleton; } public static void main(String args[]){ ThreeSingleton t1 = getInstance(); ThreeSingleton t2 = getInstance(); ThreeSingleton t3 = getInstance(); ThreeSingleton t4 = getInstance(); ThreeSingleton t5 = getInstance(); ThreeSingleton t6 = getInstance(); System.out.println(t1.toString()); System.out.println(t2.toString()); System.out.println(t3.toString()); System.out.println(t4.toString()); System.out.println(t5.toString()); System.out.println(t6.toString()); } }
清单 11 所示代码的输出如清单 12 所示:
清单 12. 清单 11 运行输出
ThreeSingleton@61de33 ThreeSingleton@14318bb ThreeSingleton@ca0b6 ThreeSingleton@61de33 ThreeSingleton@14318bb ThreeSingleton@ca0b6
第一个实例和第四个相同,第二个与第五个相同,第三个与第六个相同。也就是说一共只创建了三个实例。