单例模式的各种写法评测
单例模式(Singleton):
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
优点:
1.线程安全
2.在类加载的同时已经创建好一个静态对象,调用时反应速度快
缺点:
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化
(二)普通的懒汉式:
1 public class Singleton { 2 3 /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ 4 private static Singleton instance = null; 5 6 /* 私有构造方法,防止被实例化 */ 7 private Singleton() { 8 } 9 10 /* 静态工程方法,创建实例 */ 11 public static Singleton getInstance() { 12 if (instance == null) { 13 instance = new Singleton(); 14 } 15 return instance; 16 } 17 18 /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ 19 public Object readResolve() { 20 return instance; 21 } 22 }
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,instance对象被多条语句所操作(判断为空和创建实例);所以该类需要优化,可以加同步来解决,而加同步的方式使用同步函数和同步代码块都行,但稍微有些低效,用双重判断的方式能解决效率问题:
(三)优化后的懒汉式:
1 public static Singleton getInstance() { 2 if (instance == null) { 3 synchronized (Singleton.class) { 4 if (instance == null) { 5 instance = new Singleton(); 6 } 7 } 8 } 9 return instance; 10 }
(四)静态内部类:
1 public class Singleton { 2 3 /* 私有构造方法,防止被实例化 */ 4 private Singleton() { 5 } 6 7 /* 此处使用一个内部类来维护单例 */ 8 private static class SingletonFactory { 9 private static Singleton instance = new Singleton(); 10 } 11 12 /* 获取实例 */ 13 public static Singleton getInstance() { 14 return SingletonFactory.instance; 15 } 16 17 /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ 18 public Object readResolve() { 19 return getInstance(); 20 } 21 }
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式就显得很合理。注意:如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。而上述代码17行就是解决这个问题的方法.
实际情况中,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。但是,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
(五)枚举
1 1.public enum Singleton { 2 2. INSTANCE; 3 3. public void whateverMethod() { 4 4. } 5 5.}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
总结: