参考1:http://www.iteye.com/topic/60179
参考2:研磨设计模式
参考3:http://cantellow.iteye.com/blog/838473
参考4:http://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns (2011-11-16添加)
/** * 懒汉式单例实现的示例 */ public class Singleton { /** * 定义一个变量来存储创建好的类实例 */ private static Singleton uniqueInstance = null; /** * 私有化构造方法,好在内部控制创建实例的数目 */ private Singleton(){ // } /** * 定义一个方法来为客户端提供类实例 * @return 一个Singleton的实例 */ public static synchronized Singleton getInstance(){ //判断存储实例的变量是否有值 if(uniqueInstance == null){ //如果没有,就创建一个类实例,并把值赋值给存储类实例的变量 uniqueInstance = new Singleton(); } //如果有值,那就直接使用 return uniqueInstance; } /** * 示意方法,单例可以有自己的操作 */ public void singletonOperation(){ //功能处理 } /** * 示意属性,单例可以有自己的属性 */ private String singletonData; /** * 示意方法,让外部通过这些方法来访问属性的值 * @return 属性的值 */ public String getSingletonData(){ return singletonData; } }
package java.awt; public class Desktop { public static synchronized Desktop getDesktop(){ if (GraphicsEnvironment.isHeadless()) throw new HeadlessException(); if (!Desktop.isDesktopSupported()) { throw new UnsupportedOperationException("Desktop API is not " + "supported on the current platform"); } sun.awt.AppContext context = sun.awt.AppContext.getAppContext(); Desktop desktop = (Desktop)context.get(Desktop.class); if (desktop == null) { desktop = new Desktop(); context.put(Desktop.class, desktop); } return desktop; } }
/** * 饿汉式单例实现的示例 */ public class Singleton { /** * 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次 */ private static Singleton uniqueInstance = new Singleton(); /** * 私有化构造方法,好在内部控制创建实例的数目 */ private Singleton(){ // } /** * 定义一个方法来为客户端提供类实例 * @return 一个Singleton的实例 */ public static Singleton getInstance(){ //直接使用已经创建好的实例 return uniqueInstance; } /** * 示意方法,单例可以有自己的操作 */ public void singletonOperation(){ //功能处理 } /** * 示意属性,单例可以有自己的属性 */ private String singletonData; /** * 示意方法,让外部通过这些方法来访问属性的值 * @return 属性的值 */ public String getSingletonData(){ return singletonData; } }
package java.lang; public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } private Runtime() {} }
所谓饿汉式,既然饿,那么在创建单例对象实例(uniqueInstance)的时候就比较着急,饿了嘛,于是就在装载类的时候就创建单例对象实例(uniqueInstance),写法如下:
private static Singleton uniqueInstance = new Singleton();所谓懒汉式,既然懒,那么创建单例的对象实例的时候就不着急,会一直等到即将要使用单例对象实例的时候才会创建,懒人嘛,总会推脱不开的时候才采取真正执行工作,因此在装载对象的时候不创建对象实例,写法如下:
private static Singleton uniqueInstance = null;
懒汉式是在调用对象方法getInstance()的时候采取创建单例对象实例uniqueInstance,写法如下所示:
public static synchronized Singleton getInstance(){ //判断存储实例的变量是否有值 if(uniqueInstance == null){ //如果没有,就创建一个类实例,并把值赋值给存储类实例的变量 uniqueInstance = new Singleton(); } //如果有值,那就直接使用 return uniqueInstance; }
单例AppConfig
import java.io.*; import java.util.*; /** * 读取应用配置文件,单例实现 */ public class AppConfig { /** * 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次 */ private static AppConfig instance = new AppConfig(); /** * 定义一个方法来为客户端提供AppConfig类的实例 * @return 一个AppConfig的实例 */ public static AppConfig getInstance(){ return instance; } /** * 用来存放配置文件中参数A的值 */ private String parameterA; /** * 用来存放配置文件中参数B的值 */ private String parameterB; public String getParameterA() { return parameterA; } public String getParameterB() { return parameterB; } /** * 私有化构造方法 */ private AppConfig(){ //调用读取配置文件的方法 readConfig(); } /** * 读取配置文件,把配置文件中的内容读出来设置到属性上 */ private void readConfig(){ Properties p = new Properties(); InputStream in = AppConfig.class.getResourceAsStream("AppConfig.properties"); try { p.load(in); //把配置文件中的内容读出来设置到属性上 this.parameterA = p.getProperty("paramA"); this.parameterB = p.getProperty("paramB"); } catch (IOException e) { System.out.println("装载配置文件出错了,具体堆栈信息如下:"); e.printStackTrace(); } } }客户端Client
public class Client { public static void main(String[] args) { //创建读取应用配置的对象 AppConfig config = AppConfig.getInstance(); String paramA = config.getParameterA(); String paramB = config.getParameterB(); System.out.println("paramA="+paramA+",paramB="+paramB); } }配置文件AppConfig.properties
paramA=a1 paramB=b2
在前面的代码实例中我们可以看到, 获取对象实例的这个方法getInstance()是一个实例方法,也就是说客户端要想调用这个方法,需要先得到实例,然后才可以通过这个实例来调用getInstance()方法。可是这个方法本身就是为了得到类实例的,这样就形成了一个死循环,成了典型的“先有鸡还是先有蛋”的问题。要解决这个问题也很简单,只需要在getInstance()方法前面加上static,这样就可以直接通过类来调用这个方法,而不需要先得到实例。
因为成员变量uniqueInstance在静态方法中被使用,那么这个成员变量被迫称为一个类变量,要强制加上static。也就是说,在懒汉式实现中的uniqueInstance并没有使用static的特性。懒汉式的完整实现如下所示,为了便于理解,在注释中标示了代码的先后顺序:
public class Singleton { //4:定义一个变量来存储创建好的类实例 //5:因为这个变量要在静态方法中使用,所以需要加上static修饰 private static Singleton instance = null; //1:私有化构造方法,好在内部控制创建实例的数目 private Singleton(){ } //2:定义一个方法来为客户端提供类实例 //3:这个方法需要定义成类方法,也就是要加static public static Singleton getInstance(){ //6:判断存储实例的变量是否有值 if(instance == null){ //6.1:如果没有,就创建一个类实例,并把值赋值给存储类实例的变量 instance = new Singleton(); } //6.2:如果有值,那就直接使用 return instance; } }
3.2饿汉式实现
Java中static的特性:这就意味着,在java中,static变量只会被初始化一次,就是在类装载的时候,而且多个实例都会共享这个内存,这不就是单例模式要实现的吗?利用static的特性,我们就能够控制值创造一个实例。代码的具体写法如下所示,同样标注了代码的先后顺序:
public class Singleton { //4:定义一个静态变量来存储创建好的类实例 //直接在这里创建类实例,只会创建一次 private static Singleton instance = new Singleton(); //1:私有化构造方法,好在内部控制创建实例的数目 private Singleton(){ } //2:定义一个方法来为客户端提供类实例 //3:这个方法需要定义成类方法,也就是要加static public static Singleton getInstance(){ //5:直接使用已经创建好的实例 return instance; } }
从线程安全上来讲,不加同步的懒汉式是线程不安全的,3.1中的懒汉式实现就是线程不安全的。饿汉式是线程安全的,因为虚拟机保证只装载一次,在装载类的时候是不会发生并发的。那么如何实现懒汉式的线程安全呢?只需要加上synchronized 关键字即可,写法如下(可以参考2.1中的写法):
public static synchronized Singleton getInstance(){}但是这种写法有一个弊端,那就是每次调用这个getInstance()方法的时候都需要同步,影响性能。更好的方法是使用 双重检查加锁机制。这种方法即能保证线程安全,又能够使性能不受太大影响。
所谓双重检查加锁,值得就是:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查视力是否存在,如果不存在,就在同步的情况下创建一个实例,这就是第二重检查。这样一来,只需要同步一次即可(因为同步完必定能够创建一个实例,后面instance就不为null了。),从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,他的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理变量。
public class Singleton { /** * 对保存实例的变量添加volatile的修饰 */ private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized(Singleton.class){ //再次检查实例是否存在,如果不存在才真的创建实例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
5.更好的单例实现模式
前面介绍的几种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能够实现延迟加载,又能够实现线程安全?确实存在这样一种解决方案,我们将它称之为Lazy initialization holder class模式,这个模式综合使用了java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。
public class Singleton { /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系, * 而且只有被调用到才会装载,从而实现了延迟加载 */ private static class SingletonHolder{ /** * 静态初始化器,由JVM来保证线程安全 */ private static Singleton instance = new Singleton(); } /** * 私有化构造方法 */ private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.instance; } }
在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
在5.1的第四种单例模式示例中,当getInstance方法第一次被调用的时候,它第一次读取 SingletonHolder.instance,导致类级内部类SingletonHolder得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域(也就是static修饰的内容),从而创建Singleton的实例,代码如下所示:
private static Singleton instance = new Singleton();由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并有虚拟机来保证它的线程安全。
这种方式利用了classloder的机制来保证初始化instance时只有一个线程,它跟第二种方式不同的是(很细微的差别):第二种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比二种方式就显得很合理。
/** * 使用枚举来实现单例模式的示例 */ public enum Singleton { /** * 定义一个枚举的元素,它就代表了Singleton的一个实例 */ uniqueInstance; /** * 示意方法,单例可以有自己的操作 */ public void singletonOperation(){ //功能处理 } }
2.4使用恶汉式单例模式写了一个读取配置文件的案例,接下来我们使用枚举单例模式改写2.4的实例,完成同样的功能,也就是读取配置文件。只对AppConfig.java进行修改,其他的比如Client和配置文件都不做任何修改。实例如下:
import java.io.*; import java.util.*; public enum AppConfig { instance ; private String parameterA; private String parameterB; public String getParameterA() { return parameterA; } public String getParameterB() { return parameterB; } //构造函数 private AppConfig() { readConfig();//在构造函数中读取配置文件。 } //读取配置文件 private void readConfig() { Properties p = new Properties(); InputStream in = AppConfig.class.getResourceAsStream("AppConfig.properties"); try { p.load(in); this.parameterA = p.getProperty("paramA"); this.parameterB = p.getProperty("paramB"); } catch (IOException e) { System.out.println("装载配置文件出错了,具体堆栈信息如下:"); e.printStackTrace(); } } }