精华帖 (2) :: 良好帖 (2) :: 新手帖 (1) :: 隐藏帖 (0)
|
|||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-25
最后修改:2010-04-25
本人学习java单例模式(Singleton pattern)做的笔记,拿出来分享给像我这样的菜鸟们。希望对菜鸟们有一点点的帮助。 本人参考了:Balan的文章 Java Singleton 实用教程(附源码) 原文地址:http://balan.iteye.com/blog/164873 一、定义 单例模式(Singleton pattern):确保一个类只有一个实例,并提供一个全局的访问点。 这个定义包含两层意思: 第一:我们把某个类设计成自己管理的一个单独实例,同时也要避免其他类再自行产生实例。要想取得单个实例,通过单例类是唯一的途径。 第二:我们必需提供对这个实例的全局访问点:当你需要实例时,向类查询,它会给你返回单个实例。 注意:单例模式确保一个类只有一个实例,是指在特定系统范围内只能有一个实例。有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。 1。某个框架容器内:如Spring IOC容器,可以通过配置保证实例在容器内的唯一性。 2。再如单一JVM中、单一类加载器加载类的情况可以保证实例的唯一性。 如果在两个类加载器或JVM中,可能他们有机会各自创建自己的单个实例,因为每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的 类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单例上,就会产后多个Singleton并存的怪异现象。所以如果你的程序有 多个类加载,同时你又使用了单例模式,请一定要小心。有一个解决办法是,自行给单例类指定类加载器(指定同一个类加载器)。 二、用处 有一些对象其实我们完全只需要一个即可,如:线程池(threadpool)、缓存(cache)、注册表(registry)的对象、设备的驱动 程序的对象等等。事实上,这些类的对象只能有一个实例,如果制造出多个实例,就会导致许多问题的产生,例如:程序的行为异常、资源的过量使用、产生不一致 的结果等等。Java Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回 收(garbage collection)。我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。 三、Java Singleton模式常见的几种形式 一)。使用立即创建实例,而不用延迟实例化的做法 1。使用全局变量 //Singleton with final field public class Singleton { public static final Singleton uniqueInstance = new Singleton(); private Singleton(){ } //...Remainder omitted } 在这种方法中,公有静态成员是一个final域(保证了总是包含相同的对象引用)。私有构造函数仅被调用一次,用来实例化公有的静态final域 Singleton.uniqueInstace。由于缺少公有的或者受保护的构造函数,所有保证了Singleton的全局唯一性:一旦 Singleton类被实例化之后,只有一个Singleton实例存在——不多也不少。使用此Singleton类的程序员的任何行为都不能改变这一点。 2。使用公有的静态工厂方法 //Singleton with static factory public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton(){ } public static Singleton getInSingleton(){ return uniqueInstance; } //...Remainder omitted } 第二种方法提供了一个公有的静态工厂方法,而不是公有的静态final域。利用这个做法,我们依赖JVM在加载这个类时马上创建此类唯一的一个实例。JVM保证任何线程访问uniqueInstance静态变量之前,一定先创建此实例。 二)。使用延迟实例化的做法(使用公有的静态工厂方法) 1。非线程安全的 public class Singleton { private static Singleton uniqueInstance ; private Singleton(){ } public static Singleton getInSingleton(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } //...Remainder omitted } 先利用一个静态变量uniqueInstance来记录Singleton类的唯一实例,当我们要使用它的实例时,如果它不存在,就利用私有的构 造器产生一个Singleton类的实例并把它赋值到uniqueInstance静态变量中。而如果我们不需要使用这个实例,它就永远不会产生。这就 是"延迟实例化(lazy instantiaze)"。但上面这段程序在多线程环境中是不能保证单个实例的。分析如下:
如上分析所示,它已产生了两个Singleton实例。 2。多线程安全的 public class Singleton { private static Singleton uniqueInstance ; private Singleton(){ } public synchronized static Singleton getInSingleton(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } //...Remainder omitted } 通过给getInstance()方法增加synchronized关键字,也就是给getInstance()方法线程加锁,迫使每次只能有一 个线程在进入这个方法,这样就可以解决上面多线程产生的灾难了。但加锁的同步方法可能造成程序执行效率大幅度下降,如果你的程序对性能的要求很高,同时你 的getInstance()方法调用的很频繁,这时可能这种设计也不符合程序要求了。其实这种加锁同步的方法用在这确实有一定的问题存在,因为对 Singleton类来说,只有在第一次执行getInstance()方法时,才真正的需要对方法进行加锁同步,因为一旦第一次设置好 uniqueInstance变量后,就不再需要同步这个方法了。之后每次调用这个方法,同步反而成了一种累赘。 3。 用"双重检查加锁",在getInstance()方法中减少使用同步: public class Singleton { // volatile关键字确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量 private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getInSingleton() { if (uniqueInstance == null) {// 检查实例,如是不存在就进行同步代码区 synchronized (Singleton.class) {// 对其进行锁,防止两个线程同时进入同步代码区 if (uniqueInstance == null) {// 双重检查,非常重要,如果两个同时访问的线程,当第一线程访问完同步代码区后,生成一个实例;当第二个已进入getInstance方法等待的线程进入同步代码区时,也会产生一个新的实例 uniqueInstance = new Singleton(); } } } return uniqueInstance; } // ...Remainder omitted } 对于双重检查加锁(Double-Checked Locking)有一篇文章解释的很深入:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 三。Sington类的序列化 为了使Singleton类变成可序列化的(serializable),仅仅实现Serializable接口是不够的。为了维护 Singleton的单例性,你必须给Singleton类提供一个readResolve方法,否则的话,一个序列化的实例,每次反序列化的时候都会产 生一个新的实例。Singleton 也不会例外。 如下所示: import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.Serializable; //Singleton with final field public class Singleton implements Serializable{ private static final long serialVersionUID = 5765648836796281035L; public static final Singleton uniqueInstance = new Singleton(); private Singleton(){ } //...Remainder omitted public static void main(String[] args) throws Exception{ //序列化 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\Singleton.obj")); Singleton singleton = Singleton.uniqueInstance; objectOutputStream.writeObject(singleton); objectOutputStream.close(); //反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\Singleton.obj")); Singleton singleton2 = (Singleton)objectInputStream.readObject(); objectInputStream.close(); //比较是否原来的实例 System.out.println(singleton==singleton2); } } 输出结果为:false 解决方法是为Singleton类增加readResolve()方法: //readResolve 方法维持了Singleton的单例属性 private Object readResolve() throws ObjectStreamException{ return uniqueInstance; } 再进行测试:输出结果为true
反序列化之后新创建的对象会先调用此方法,该方法返回的对象引用被返回,取代了新创建的对象。本质上,该方法忽略了新建对象,仍然返回类初始化时创建的那个实例。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-26
最后修改:2010-04-30
public class Singleton { private static class SingletonHolder { final static Singleton INSTANCE = new Singleton(); } private Singleton (){ } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 之前看过的某solution,jvm加载SingletonHolder的时候会初始化INSTANCE,所以既是lazy的又保证是单例的 [已修正] 感谢楼下指出问题 看来在自己亲自验证之前,不能随便贴代码~~~~ |
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-26
和effective java里讲的 有点像 很佩服 lz的笔记做这么好
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-26
二楼的也不错
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-26
向楼主学习了!
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-27
最后修改:2010-04-27
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-27
weiqiang.yang 写道 public class Singleton { private class SingletonHolder { final static Singleton INSTANCE = new Singleton(); } private Singleton (){ } public Singleton getInstance() { return SingletonHolder.INSTANCE; } } 之前看过的某solution,jvm加载SingletonHolder的时候会初始化INSTANCE,所以既是lazy的又保证是单例的 嗯,这种方法我也看到过多次,不过对这种方法不是很理解,谢谢你的补充! |
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-27
lz12366 写道 和effective java里讲的 有点像 很佩服 lz的笔记做这么好
b,g了很多文章,学习完了后,又拼凑了这篇文章,所以肯定有很多东西与别人的雷同,并且我承认这不是纯属巧合,而是我参考了别人的东西,或者直接点,就是我剽窃了别人的成果,有点无耻,呵呵! |
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-27
ryxxlong 写道 3。 用"双重检查加锁",在getInstance()方法中减少使用同步: 这个junJZ_2008在http://www.iteye.com/topic/652440中, 说由于java的无序写入,会造成失败 大家可以看看 |
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||
发表时间:2010-04-27
这个挺专业的啊 收藏学习一下
|
|||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||