复习下单例模式 我擦今天上网看了下 单例整理的好多啊 自己就跟着敲一遍 顺便说下自己的理解。
什么叫做单例?
单例就是在程序运行中,一个类存在唯一一个对象(实例)
单例第一种:懒汉式 线程不安全
//单例第一种写法:懒汉式 public class Singleton { // 命名静态的实例变量 注意是静态的想想静态变量的特点 (1)静态变量属于类 可以使用类名直接调用(2)静态变量在初始化的时候就分配内存空间 private static Singleton instance = null; // 定义构造函数 private Singleton() { System.out.println("AAA"); } /** * 静态方法 参照静态变量 不在过多解释 * @return */ public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懒汉式单例 就是很懒啊,代码很简单。在非多线程的情况下,比较安全,没有问题,但是想想多线程并发访问的时候,getInstance()方法不是线程安全的,这样的话 这种模式就不推荐了。
单例第二种:懒汉,线程安全
第一种单例既然不安全,那么我们肯定有方案解决这个问题,那就是在getInstance()方法上面加上同步关键字Synchronized(嘿嘿 这个关键字若是不知道的话 就不要继续看了 先回去复习下线程哈) 本来不想贴代码的,后来想想还是贴下代码
1 public class Singleton { 2 3 private static Singleton instance = null; 4 5 private Singleton() { 6 System.out.println("AAA"); 7 } 8 9 public static synchronized Singleton getInstance() { 10 if (instance == null) { 11 instance = new Singleton(); 12 } 13 return instance; 14 } 15 }
这样的话 单例就很安全了,不过效率低下,一般不会这么做,jdk1.5之后提供了condition方案解决同步问题,我看可以在这里尝试下
单例第三种:饿汉
public class Singleton { //知道为什么叫“饿汉”吗?看看下面这句代码 直接进行实例化 就是比较饥饿啊 哈哈 private static Singleton instance = new Singleton(); private Singleton() { System.out.println("饿汉"); }; public static Singleton getInstance() { return instance; } }
这种写法我就不进行总结了 复制了 一些网友的总结如下:
基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果
单例第四种:(饿汉的另一种方式)
1 private static Singleton instance = null; 2 3 static { 4 instance = new Singleton(); 5 } 6 7 private Singleton(){}; 8 9 public static Singleton getInstance() { 10 return instance; 11 }
不解释,没有太多意义
单例第五种:静态内部类
想想第三种“饿汉”模式的缺点:就是在刚进入的时候就进行实例化了,这样会造成资源的浪费。我们是否可以做到 在使用的时候再实例化呢?看代码
1 public class Singleton { 2 //建立静态内部类 3 private static class SingletonInnerClass { 4 private static final Singleton INSTANCE = new Singleton(); 5 } 6 /** 7 * 构造方法 8 */ 9 private Singleton(){ 10 System.out.println("静态内部类"); 11 }; 12 13 public static Singleton getInstance() { 14 return SingletonInnerClass.INSTANCE; 15 } 16 }
说说这个方法的优点,来源于网友总结:
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
第六种 枚举
1 public enum Singleton { 2 INSTANCE; 3 public void whateverMethod() { 4 } 5 }
枚举这种模式,在实际使用中 没有使用过。
Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒
第七种 双重校验锁i
1 public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
这个是第二种方式的升级版,俗称双重检查锁定,在JDK1.5之后,双重检查锁定才能够正常达到单例效果
以下总结来源网络:
有两个问题需要注意:
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
1 private static Class getClass(String classname) 2 throws ClassNotFoundException { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 5 if(classLoader == null) 6 classLoader = Singleton.class.getClassLoader(); 7 8 return (classLoader.loadClass(classname)); 9 } 10 }
对第二个问题修复的办法是:
1 public class Singleton implements java.io.Serializable { 2 public static Singleton INSTANCE = new Singleton(); 3 4 protected Singleton() { 5 6 } 7 private Object readResolve() { 8 return INSTANCE; 9 } 10 }
对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。
========================================================================
superheizai同学总结的很到位:
不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。
我很高兴有这样的读者,一起共勉。
《over》
参考网址:
http://cantellow.iteye.com/blog/838473