设计模式
概述
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了提高代码的可维护性,通用性可扩展性,并且降低代码的复杂度。
设计模式分为三种类型,共23种
- 创建型模式:单例模式、抽象工厂模式、工厂模式、原型模式、建造者模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、享元模式、外观模式、代理模式
- 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链(责任链)模式
单例模式
简介:单例设计模式,就是采取一定方法保证在整个系统中,对某个类只能存在一个对象的实例并且该类只提供一个取得对象实例的方法(静态方法)。
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目只需要一个SessionFactory,这就可以使用单例模式。
单例设计模式的八种方式
- 饿汉式(静态常量)
可用
- 饿汉式(静态代码块)
可用
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(同步代码块)
- 双重检查
推荐使用
- 静态内部类
推荐使用
- 枚举
推荐使用
饿汉式(静态常量)
// 饿汉式 写法一 静态常量 步骤如下
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、类的内部创建对象
private final static Singleton instance = new Singleton();
// 3、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
优缺点分析
优点:实现比较简单,在类加载的时候就完成了实例化。避免了线程同步问题。
缺点:在类加载的时候就完成了实例化,没有达到懒加载的效果,如果未曾使用到这个实例,会造成内存的浪费。
这种方式是基于classloader机制避免了多线程的同步问题,但是instance在类加载时就实例化,在单例模式中,大多数都是调用getInstance()方法,但是导致类加载的原因有很多种,因此不能够确定是否由其它方式(或者其它的静态方法)导致类加载,这时候初始化instance就没有达到懒加载的效果。这种单例模式可用,但是可能造成内存浪费
饿汉式(静态代码块)
// 饿汉式 写法二 静态代码块 步骤如下
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、本类内部声明对象实例
private final static Singleton instance;
// 3、静态代码块中 创建单例对象
static {
instance = new Singleton();
}
// 4、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
优缺点说明
这种方式和上面的方式类似,只是将类实例化的过程放到了静态代码块中,也就是在类加载的时候就执行静态代码块,初始化类的实例,优缺点跟上面一样。这种单例模式可用,但是可能造成内存浪费
懒汉式(线程不安全)
// 懒汉式 写法一
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、本类内部声明对象实例
private static Singleton instance;
// 3、提供一个静态的共有方法,当使用到该方法时,才去创建 instance
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明
可以起到懒加载的效果,但是只能在单线程下使用。如果在多线程下使用,一个线程进入 if (null == instance) 判读语句块,还未来的及往下执行,另一个线程也通过了这个判断语句,这是就会产生多个实例。所以多线程中不可以使用这种方法。实际开发中,不要使用这种方式。
懒汉式(线程安全,同步方法)
// 懒汉式 写法二 解决线程不安全问题
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、本类内部声明对象实例
private static Singleton instance;
// 3、提供一个静态的共有方法,当使用到该方法时,才去创建 instance
public static synchronized Singleton getInstance() { // 加重锁
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明
可以起到懒加载的效果,解决了线程不安全的问题。效率低,每个线程想要获得类实例的时候,执行getInstance()方法都要进行同步,但是这个方法只执行一次实例化代码就足够了,后面想要获得该类实例直接return就可以了,方法同步效率太低。实际开发中,不推荐使用这种方式。
懒汉式(同步代码块)
// 懒汉式 写法三 同步代码块方式
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、本类内部声明对象实例
private static Singleton instance;
// 3、提供一个静态的共有方法,当使用到该方法时,才去创建 instance
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) { // 这种方式并不能解决线程安全问题
instance = new Singleton();
}
}
return instance;
}
}
优缺点说明
可以起到懒加载的效果,不能线程不安全的问题,跟第3种写法类似,无法起到线程同步的作用,有可能会产生多个实例。实际开发中,不能使用这种方式。
双重检查
// 双重检查
// 代码实现
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、本类内部声明对象实例
private static volatile Singleton instance;
// volatile防止指令重排 保证可见行 立即刷回到主内存让其它线程
// 如果不使用 volatile 有可能会出现异常,原因 instance = new Singleton(); 并不是一个原子操作
// 线程A的操作会被编译成3条指令,(A1)分配对象的内存空间、 (A2)初始化对象、 (A3)设置instance指向的内存空间
// 但是这个被返回的instance是有问题的,它还没有被初始化->(A2)还没有执行
// 3、提供一个静态的共有方法,当使用到该方法时,才去创建 instance
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点说明
双重检查概念是多线程开发经常使用到的,如代码所示,我们进行了两次 if (null == instance)
的检查,这样可以保证线程安全。实例化代码只执行一次,后面再次访问,if (null == instance)
直接return 实例化对象,避免反复进行方法同步。线程安全,延迟加载,效率较高。实际开发中,可以使用这种方式。
静态内部类
//静态内部类 懒加载 线程安全
class Singleton {
// 1、构造器私有化 -->外部不能通过new获取对象
private Singleton() {
}
// 2、静态内部类,该类中又一个静态属性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
// 提供共有的方法获取Singleton 直接返回成员变量
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优缺点说明
这种方式采用了类加载机制来保证初始化实例时只有一个 instance,静态内部类方式在Singleton类被加载时,并不会立即实例化,而是需要实例化时,调用getInstance()方法,才会加载静态内部类SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程安全,在类的初始化时,别的线程无法进入。简单、避免了线程不安全,利用静态内部类的特点实现了延迟加载,效率较高,推荐使用。
枚举
// 使用枚举实现单例
enum Singleton{
INSTANCE;
public void say(){
System.out.println("ok~");
}
}
优缺点说明
借助JDK 1.5 提供的枚举实现单例模式,不仅可以避免线程同步问题,而且还能防止反序列化重新创建新的对象,这种方式是被 Josh Bloch 提倡使用。
枚举单例模式使用举例
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println("instance hashCode :" + instance.hashCode());
System.out.println("instance1 hashCode :" + instance1.hashCode());
instance.say();
}
单例模式在JDK中的使用
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
......
}
Runtime类使用的的是饿汉式的单例模式,Runtime类在运行时是肯定需要使用到的类。
单例模式注意事项和细节说明
单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。想要实例化一个对象的时候,必须使用相应的获取对象的方法,不能使用new。单例模式的使用场景:需要频繁创建和销毁的对象、创建对象时耗时或耗费资源过多,但是又经常使用到的对象:工具类对象、频繁访问数据库或者文件的对象。
// TODO 思考
工具类方法一般会声明成为静态方法,在类加载的时候就加载到内存中,当然也可以将工具类改造成为单例的类。那么我们哪些工具类可以使用单例更好、哪些工具类直接使用静态方法好?