设计模式概述
设计模式(Design Patterns)在软件开发中非常出名,一个设计模式很好的描述了一类通用的软件问题的解决方案。
以下是使用设计模式的一些好处:
- 设计模式是已经设计好的提供一种工业化的标准方案解决通用的问题,所以直接使用它能节省很多时间。
- 使用设计模式提升了复用性,加强了代码的健壮性和可维护性,减少了软件产品的总体成本。
- 使用设计模式是我们的代码更加容易理解和调试,让我们更快的开发新的模块,团队的新成员更快的理解现有的模块。
java设计模式可以被分为三类:创建型,结构型,行为型
创建型设计模式
创建型设计模式能够在不同的应用场景上提供一种最好的方法来实例化一个对象。
基本的对象实例化很容易导致设计问题或者增加了设计上的不可预期的问题。创建型设计模式通过使用不同的方法来控制对象的创建,以解决上述问题。
这里会有五种创建型设计模式:
- 单例模式(Singleton Pattern)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 原型模式(Prototype Pattern)
本篇主要讲一下单例模式。
单例模式
Singleton 是 GoF 设计模式中的一种。从定义上看,它似乎是一种非常简单的设计模式,但它有很多种不同的实现方式。
单例模式约束了类的实例化过程,确保 Java虚拟机中只有一个实例存在。单例模式类必须提供一个全局访问点来获取到这个类的实例。单例模式一般运用于日志、驱动对象、缓存和线程池。
单例模式一般也用于其他的设计模式如抽象工厂模式、建造者模式、原型模式、外观模式等。它还被用于核心Java类中如java.lang.Runtime ,java.awt.Desktop。
我们有很多方法实现单例模式,但它们都有以下共同的特点:
- 一个私有的构造器防止别的类创建它的实例。
- 一个私有的成员变量,变量的类型为这个类本身,它是这个类唯一的实例对象。
- 一个静态的public方法返回这个类的实例,它就是外部世界访问这个类实例的全局访问点。
接下来,提供几个不同的单例模式实现方式,以及它们各自需要注意的地方
饿汉模式(Eager Initialization)
饿汉模式在类加载的时候就创建它的实例对象,这是创建单例类最简单的一种方式。但是它有一个缺点就是客户端程序可能永远不会用到它的实例,这样造成了资源的浪费。
1 | package com.tisa.singleton; |
如果单例类没有使用太多资源,这倒是一种可行的实现方式。但在大多数情境下,单例类的创建需要很多资源如文件系统资源,数据库连接等等。合适的方式是在客户端程序使用getInstance方法的时候再创建类的实例。同时,饿汉模式也没有提供异常处理的地方。
静态代码块初始化(Static Block Initialization)
静态代码块初始化的方式与饿汉模式很相似,但前者能够在静态代码块中进行异常处理操作。
1 | package com.tisa.singleton; |
上面两种实现方式都是在使用类的实例之前就实例化了,这不是一种实用的实现方式。
懒汉模式
1 | package com.tisa.singleton; |
上面这种实现方式在单线程环境中能正常的工作,但在多线程环境下就可能导致一些问题,如果同时有两个线程进入if语句中,就会使得两个线程得到不同的实例。
线程安全的单例模式
这种方式很简单创建出线程安全的单例模式,通过使用 synchronized关键字。
1 | package com.tisa.singleton; |
上面的实现方式解决了线程安全的问题,但是因为同步方法的通信使得性能下降。为了避免这种额外的开销,一般使用双重检查。
1 | public static ThreadSafeSingleton getInstanceUsingDoubleLocking() { |
Bill Pugh Singleton
在java 5 之前,java的内存模型存在很多问题,当大量的线程同一时间请求单例类的实例时会出现一些问题。所以Bill Pugh 想出了一种不通的方式创建单例类:使用静态内部帮助类。
1 | package com.tisa.singleton; |
注意:private static class包含了单例类的实例。当单例类加载的时候,SingletonHelper还没有加载,仅仅当有人调用 getInstance方法的时候,这个类才加载并且创建单例类的实例。
这种方法被最广泛的使用,因为它不需要使用同步代码块。
使用反射可以破环单例模式
使用反射能够破坏上面提到的所有单例模式的实现方法。
1 | package com.tisa.singleton; |
上面这个方法示例说明单例模式可以被反射破坏。反射一般用于框架的设计。
Enum Singleton
为了克服反射的影响,可以使用枚举实现单例模式,确保了每个枚举值在java程序里只被实例化一次。它的缺点是不太灵活,例如,它不允许懒加载。
1 | package com.tisa.singleton; |
Serialization and Singleton
有时候在分布式系统中,我们需要为单例类实现序列化接口以便我们能将它的状态存储到文件系统,然后在某个点恢复它。
1 | package com.tisa.singleton; |
上面序列化单例类存在的问题是,反序列化这个类它的时候,总是会创建一个新的实例:
1 | package com.tisa.singleton; |
它也破坏了单例模式,为了避免这种情景,我们只需要提供一个readResolve()方法实现就可以了。
1 | protected Object readResolve() { |