java设计模式之单例模式(Singleton pattern)
单例模式的定义:
Singleton pattern restricts the instantiation of a class and ensures that only one instance of the class exists in the java virtual machine. The singleton class must provide a global access
point to get the instance of the class.
限制一个类的实例在一个jvm实例中确保只有一个,而且必须提供一个全局访问点获得该单例。
为什么会出现单例模式:
- 减少内存开支。由于单例在内存中相对于一个jvm实例内只有一个实例对象,不会重复的创建和jvm垃圾回收,对于内存减少了空间占用,也利于jvm垃圾回收处理。
- 减少系统性能开销。当一个对象的产生依赖较多的资源时,比如读取配置或者依赖其他对象的时候,单例在jvm启动的时候预加载资源,然后可以永久驻留内存,当然也减少了jvm的垃圾回收线程的负担。
- 当然还有很有的优势,现流行的spring框架就是默认支持单例模式(相对应spring容器)。
单例的实现方式:
实现单例模式的方式有很多不同手段,但以下几点我们会同时考虑:
- 构造函数必须私有,不能让别人有权限随意实例化
- 该单例一般在类中有一个私有静态变量
- 该单例一般提供一个静态公共方法获得该单例(对于外界的该单例的全局访问点)
饿汉式(Eager initialization)
饿汉式单例实现方式是在类加载的时候初始化该单例。这种方式实现单例最简单,但也有个缺点就是即使我们应用中没有使用该类的单例,但类加载的时候也必须初始化。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Created on 2016年7月31日 下午11:36:05 * * 饿汉式 单例 * EagerInitialized Singleton */ public class EagerInitializedSingleton { private static final EagerInitializedSingleton instance = new EagerInitializedSingleton(); private EagerInitializedSingleton() { } public static EagerInitializedSingleton getInstance() { return instance; } public void doSomething() { System.out.println("test"); } public static void main(String[] args) { System.out.println(EagerInitializedSingleton.getInstance()); System.out.println(EagerInitializedSingleton.getInstance()); EagerInitializedSingleton.getInstance().doSomething(); // [email protected]139a55 // [email protected]139a55 // test System.out.println(EagerInitializedSingleton.getInstance() == EagerInitializedSingleton.getInstance()); // true } }
当单例没有涉及到过多的资源使用的时候,饿汉式单例比较适合。但很多场景下,单例的使用一般是为了使用一些资源比如文件系统、数据库连接等等,这中场景下,一般我们尽量使得该单例必须使用的时候,才会初始化,以避免资源的浪费使用。而且饿汉式单例也没提供异常的处理机制。
Static block initialization
静态块初始化单例和饿汉式单例相似,差别在于实例的初始化在静态块中,这中方式提供了异常处理。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Created on 2016年8月1日 上午12:30:08 */ public class StaticBlockSingleton { private static StaticBlockSingleton instance; public static StaticBlockSingleton getInstance() { return instance; } static { try { instance = new StaticBlockSingleton(); } catch (Exception e) { throw new RuntimeException("Exception occured in creating singleton instance"); } } public static void main(String[] args) { System.out.println(StaticBlockSingleton.getInstance()); System.out.println(StaticBlockSingleton.getInstance()); System.out.println(StaticBlockSingleton.getInstance() == StaticBlockSingleton.getInstance()); // [email protected]5 // [email protected]5 // true } }
Lazy Initialization
懒初始化方法承担了单例的创建,并且是获取该单例的入口点。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Created on 2016年8月1日 上午12:39:40 */ public class LazyInitializedSingleton { private static LazyInitializedSingleton instance; public static LazyInitializedSingleton getInstance() { if (instance == null) { instance = new LazyInitializedSingleton(); } return instance; } private LazyInitializedSingleton() { } public static void main(String[] args) { System.out.println(LazyInitializedSingleton.getInstance() == LazyInitializedSingleton.getInstance());// true } }
上面几种单例的实现在单线程环境下可以很好的工作,但可能面临多线程安全问题。
Thread Safe Singleton
线程安全单例,简单的线程安全我们可以用
synchronized
实现。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Created on 2016年8月1日 上午11:19:36 * * Thread Safe Singleton * */ public class ThreadSafeSingleton { private static ThreadSafeSingleton instance; public static synchronized ThreadSafeSingleton getInstance() { if (instance == null) { instance = new ThreadSafeSingleton(); } return instance; } private ThreadSafeSingleton() { } public static void main(String[] args) { System.out.println(ThreadSafeSingleton.getInstance() == ThreadSafeSingleton.getInstance()); // true } }
上面的线程安全锁的粒度是比较大了,锁的粒度越大,并发性约不能,导致性能下降,我们可以用double checked locking
规则减少锁的粒度。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Created on 2016年8月1日 上午11:37:25 * * double checked locking principle */ public class DoubleCheckedLockingThreadSafeSingleton { private static DoubleCheckedLockingThreadSafeSingleton instance; public static DoubleCheckedLockingThreadSafeSingleton getInstance() { if (instance == null) { synchronized (DoubleCheckedLockingThreadSafeSingleton.class) { if (instance == null) { instance = new DoubleCheckedLockingThreadSafeSingleton(); } } } return instance; } private DoubleCheckedLockingThreadSafeSingleton() { } public static void main(String[] args) { } }
Inner
static helper class Singleton
采用内部类来实现资源的懒加载:
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Inner static helper class Singleton * * * Created on 2016年8月1日 上午11:54:05 */ public class InnerStaticHelperClassSingleton { private InnerStaticHelperClassSingleton() { } public static InnerStaticHelperClassSingleton getInstance() { return SingletonHelper.instance; } private static class SingletonHelper { private static final InnerStaticHelperClassSingleton instance = new InnerStaticHelperClassSingleton(); } public static void main(String[] args) { } }
内部类拥有外部类的实例,当外部类被jvm加载的时候,内部类没有被加载,外部类的实例也就没有被实例化,当外部类真正的调用得到单例入口方法的时候,才会触发内部类的加载,同时外部类的单例也就初始化一次。
这种方式的好处就是不用锁同步,就可以实现线程安全的单例。
枚举单例实现Enum Singleton
上面的单例实现可能由于反射导致不能保证一个类只有一个实例。下面的枚举实现方式克服了这个问题,但这种实现放手不能延迟加载。
package com.doctor.design_pattern.singleton; /** * @author sdcuike * * Enum Singleton * 枚举实现单例不能延迟加载资源,但保证了enum值只实例化一次。而且克服了反射带来的问题 * * Created on 2016年8月1日 下午12:24:06 */ public enum EnumSingleton { instance; private EnumSingleton() { } public void doSomething() { System.out.println("test do "); } public static void main(String[] args) { EnumSingleton.instance.doSomething(); } }
序列化与单例
我们一般通过网络交互的时候都会用到序列化,序列化打破了单例的规则,反序列化我们得到了另一个实例(请自行验证)。为了保证序列化不影响单例规则,我们一般实现以下方法:
protected Object readResolve() { return getInstance(); }
具体请参考资料:http://docs.oracle.com/javase/1.5.0/docs/guide/serialization/spec/input.html