Java实现单例模式.doc_第1页
Java实现单例模式.doc_第2页
Java实现单例模式.doc_第3页
Java实现单例模式.doc_第4页
Java实现单例模式.doc_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2009年05月14日 星期四 12:00Java设计模式圣经连载(04)单例模式 单例模式是一种常见的设计模式,在Java与模式一书中,阎宏博士对单例模式做了全面的总结。单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。单例模式有一下特点:1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供这一实例。说明:一下的代码来自阎宏博士的Java与模式一书,其中对一些类的写法做调整(符合Java1.5的习惯),另外还加了测试方法。一、懒汉式单例在类被加载的时候,唯一实例已经被创建。这个设计模式在Java中容易实现,在别的语言中难以实现。/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2007-9-11* Time: 14:57:08* 单例模式-懒汉式单例*/public class LazySingleton /* * 私有静态对象,加载时候不做初始化 */ private static LazySingleton m_intance=null; /* * 私有构造方法,避免外部创建实例 */ private LazySingleton() /* * 静态工厂方法,返回此类的唯一实例. * 当发现实例没有初始化的时候,才初始化. * return LazySingleton */ synchronized public static LazySingleton getInstance() if(m_intance=null) m_intance=new LazySingleton(); return m_intance; 二、饿汉式单例在类加载的时候不创建单例实例。只有在第一次请求实例的时候的时候创建,并且只在第一次创建后,以后不再创建该类的实例。/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2007-9-11* Time: 14:45:25* 单例模式-饿汉式单例*/public class EagerSingleton /* * 私有的(private)唯一(static final)实例成员,在类加载的时候就创建好了单例对象 */ private static final EagerSingleton m_instance = new EagerSingleton(); /* * 私有构造方法,避免外部创建实例 */ private EagerSingleton() /* * 静态工厂方法,返回此类的唯一实例. * return EagerSingleton */ public static EagerSingleton getInstance() return m_instance; 三、登记式单例这个单例实际上维护的是一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从工厂直接返回,对于没有登记的,则先登记,而后返回。/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2005-9-11* Time: 15:20:16* 单例模式- 登记式单例*/public class RegSingleton /* * 登记薄,用来存放所有登记的实例 */ private static Map m_registry = new HashMap(); /在类加载的时候添加一个实例到登记薄 static RegSingleton x = new RegSingleton(); m_registry.put(x.getClass().getName(), x); /* * 受保护的默认构造方法 */ protected RegSingleton() /* * 静态工厂方法,返回指定登记对象的唯一实例; * 对于已登记的直接取出返回,对于还未登记的,先登记,然后取出返回 * param name * return RegSingleton */ public static RegSingleton getInstance(String name) if (name = null) name = RegSingleton; if (m_registry.get(name) = null) try m_registry.put(name, (RegSingleton) Class.forName(name).newInstance(); catch (InstantiationException e) e.printStackTrace(); catch (IllegalAccessException e) e.printStackTrace(); catch (ClassNotFoundException e) e.printStackTrace(); return m_registry.get(name); /* * 一个示意性的商业方法 * return String */ public String about() return Hello,I am RegSingleton!; 四、单例模式的一个应用该应用是配置文件管理类。为了本例能正确运行,我在C盘下先建立了一个perties文件,内容如下:-user=rootpassword=leizhimin这个配置文件管理类的代码如下:/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2005-9-11* Time: 15:55:01* 单例模式应用-单例类应用-配置文件管理*/public class ConfigManager /* * 属性文件全名 */ private static final String PFILE = C:perties; /* * 对应于属性文件的文件对象变量 */ private File m_file = null; /* * 属性文件的最后修改日期 */ private long m_lastModifiedTime = 0; /* * 属性文件所对应的属性对象变量 */ private Properties m_props = null; /* * 本类可能存在的唯一的一个实例 */ private static ConfigManager m_instance = new ConfigManager(); /* * 私有构造子,用以保证外界无法直接实例化 */ private ConfigManager() m_file = new File(PFILE); m_lastModifiedTime = m_file.lastModified(); if (m_lastModifiedTime = 0) System.err.println(PFILE + file does not exist!); m_props = new Properties(); try m_props.load(new FileInputStream(PFILE); catch (IOException e) e.printStackTrace(); /* * 静态工厂方法 * * return ConfigManager */ synchronized public static ConfigManager getInstance() return m_instance; /* * 获取属性配置项的值 * * param name * param defaultVal * return Object */ public final Object getConfigItem(String name, Object defaultVal) long newTime = m_file.lastModified(); if (newTime = 0) /属性文件不存在 if (m_lastModifiedTime = 0) System.err.println(PFILE + file does not exist!); else System.err.println(PFILE + file was deleted!); return defaultVal; else if (newTime m_lastModifiedTime) m_props.clear(); try m_props.load(new FileInputStream(PFILE); catch (IOException e) e.printStackTrace(); m_lastModifiedTime = newTime; Object val = m_props.getProperty(name); if (val = null) return defaultVal; else return val; 测试配置文件类:/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2007-9-11* Time: 16:42:45* 配置文件管理类测试*/public class Test_ConfigManager public static void main(String args) ConfigManager cfgm = ConfigManager.getInstance(); Object val1 = cfgm.getConfigItem(sdf, leizhimin); Object val2 = cfgm.getConfigItem(user, leizhimin); System.out.println(val1.toString(); System.out.println(val2.toString(); 运行结果:leizhiminrootProcess finished with exit code 0五、笔者写的一个JDBC数据库工具类的单例实现/* Created by IntelliJ IDEA.* User: leizhimin* Date: 2005-9-11* Time: 18:04:46* 单例模式在JDBC编程中的应用,用于设计数据库工具类*/public class DBUtil /单一实例 private static final DBUtil _instance = new DBUtil(); /数据源的JNDI private static final String datasource = java:comp/env/jdbc/zvfims; /* * 私有构造方法,防止外部实例化 */ private DBUtil() /* * 数据库工具类实例工厂 * * return DBUtil */ public DBUtil getInstance() return _instance; /* * 业务方法:用于获取数据库连接 * * return Connection */ public Connection makeConnection() Connection conn = null; try Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup(datasource); conn = ds.getConnection(); catch (NamingException e) System.out.println(获取数据源异常,请AppServer的JNDI数据源配置!); e.printStackTrace(); catch (SQLException e) System.err.println(获取数据库连接发生异常!); e.printStackTrace(); return conn; 通过这个单例类和开放的业务方法,可以为整个系统应用提供数据库连接。深入Java单例模式浅析作者: 佚名, 出处:IT专家网,责任编辑: 谢妍妍, 2009-09-22 13:00在GoF的23种设计模式中,单例模式是比较简单的一种。然而,有时候越是简单的东西越容易出现问题。下面就单例设计模式详细的探讨一下。 在GoF的23种设计模式中,单例模式是比较简单的一种。然而,有时候越是简单的东西越容易出现问题。下面就单例设计模式详细的探讨一下。所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。就像是Java Web中的application,也就是提供了一个全局变量,用处相当广泛,比如保存全局数据,实现全局性的操作等。1. 最简单的实现首先,能够想到的最简单的实现是,把类的构造函数写成private的,从而保证别的类不能实例化此类,然后在类中提供一个静态的实例并能够返回给使用者。这样,使用者就可以通过这个引用使用到这个类的实例了。publicclassSingletonClass privatestaticfinalSingletonClassinstance=newSingletonClass(); publicstaticSingletonClassgetInstance() returninstance; privateSingletonClass() 如上例,外部使用者如果需要使用SingletonClass的实例,只能通过getInstance()方法,并且它的构造方法是private的,这样就保证了只能有一个对象存在。2. 性能优化lazy loaded上面的代码虽然简单,但是有一个问题无论这个类是否被使用,都会创建一个instance对象。如果这个创建过程很耗时,比如需要连接10000次数据库(夸张了:-),并且这个类还并不一定会被使用,那么这个创建过程就是无用的。怎么办呢?为了解决这个问题,我们想到了新的解决方案:publicclassSingletonClass privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance() if(instance=null) instance=newSingletonClass(); returninstance; privateSingletonClass() 代码的变化有两处首先,把instance初始化为null,直到第一次使用的时候通过判断是否为null来创建对象。因为创建过程不在声明处,所以那个final的修饰必须去掉。我们来想象一下这个过程。要使用SingletonClass,调用getInstance()方法。第一次的时候发现instance是null,然后就新建一个对象,返回出去;第二次再使用的时候,因为这个instance是static的,所以已经不是null了,因此不会再创建对象,直接将其返回。这个过程就成为lazy loaded,也就是迟加载直到使用的时候才进行加载。3. 同步上面的代码很清楚,也很简单。然而就像那句名言:“80%的错误都是由20%代码优化引起的”。单线程下,这段代码没有什么问题,可是如果是多线程,麻烦就来了。我们来分析一下:线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象单例失败!解决的方法也很简单,那就是加锁: publicclassSingletonClass privatestaticSingletonClassinstance=null; publicsynchronizedstaticSingletonClassgetInstance() if(instance=null) instance=newSingletonClass(); returninstance; privateSingletonClass() 是要getInstance()加上同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。4. 又是性能上面的代码又是很清楚很简单的,然而,简单的东西往往不够理想。这段代码毫无疑问存在性能的问题synchronized修饰的同步块可是要比一般的代码段慢上几倍的!如果存在很多次getInstance()的调用,那性能问题就不得不考虑了!让我们来分析一下,究竟是整个方法都必须加锁,还是仅仅其中某一句加锁就足够了?我们为什么要加锁呢?分析一下出现lazy loaded的那种情形的原因。原因就是检测null的操作和创建对象的操作分离了。如果这两个操作能够原子地进行,那么单例就已经保证了。于是,我们开始修改代码: publicclassSingletonClass privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance() synchronized(SingletonClass.class) if(instance=null) instance=newSingletonClass(); returninstance; privateSingletonClass() 首先去掉getInstance()的同步操作,然后把同步锁加载if语句上。但是这样的修改起不到任何作用:因为每次调用getInstance()的时候必然要同步,性能问题还是存在。如果如果我们事先判断一下是不是为null再去同步呢?publicclassSingletonClass privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance() if(instance=null) synchronized(SingletonClass.class) if(instance=null) instance=newSingletonClass(); returninstance; privateSingletonClass() 还有问题吗?首先判断instance是不是为null,如果为null,加锁初始化;如果不为null,直接返回instance。这就是double-checked locking设计实现单例模式。到此为止,一切都很完美。我们用一种很聪明的方式实现了单例模式。5. 从源头检查下面我们开始说编译原理。所谓编译,就是把源代码“翻译”成目标代码大多数是指机器代码的过程。针对Java,它的目标代码不是本地机器代码,而是虚拟机代码。编译原理里面有一个很重要的内容是编译器优化。所谓编译器优化是指,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。这个过程成为reorder。要知道,JVM只是一个标准,并不是实现。JVM中并没有规定有关编译器优化的内容,也就是说,JVM实现可以自由的进行编译器优化。下面来想一下,创建一个变量需要哪些步骤呢?一个是申请一块内存,调用构造方法进行初始化操作,另一个是分配一个指针指向这块内存。这两个操作谁在前谁在后呢?JVM规范并没有规定。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。下面我们来考虑这么一种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了!于是,我们想到了下面的代码:publicclassSingletonClass privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance() if(instance=null) SingletonClasssc; synchronized(SingletonClass.class) sc=instance; if(sc=null) synchronized(SingletonClass.class) if(sc=null) sc=newSingletonClass(); instance=sc; returninstance; privateSingletonClass() 我们在第一个同步块里面创建一个临时变量,然后使用这个临时变量进行对象的创建,并且在最后把instance指针临时变量的内存空间。写出这种代码基于以下思想,即synchronized会起到一个代码屏蔽的作用,同步块里面的代码和外部的代码没有联系。因此,在外部的同步块里面对临时变量sc进行操作并不影响instance,所以外部类在instance=sc;之前检测instance的时候,结果instance依然是null。不过,这种想法完全是错误的!同步块的释放保证在此之前也就是同步块里面的操作必须完成,但是并不保证同步块之后的操作不能因编译器优化而调换到同步块结束之前进行。因此,编译器完全可以把instance=sc;这句移到内部同步块里面执行。这样,程序又是错误的了!6. 解决方案说了这么多,难道单例没有办法在Java中实现吗?其实不然!在JDK 5之后,Java使用了新的内存模型。volatile关键字有了明确的语义在JDK1.5之前,volatile是个关键字,但是并没有明确的规定其用途被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了。publicclassSingletonClass privatevolatilestaticSi

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论