本来关于设计模式是打算在面向对象的设计原则写完之后写的,因为训练营有一道题是手写单例模式,于是干脆就先把单例模式总结一下,其它的后面再继续写。
什么是设计模式?
总的来说,设计模式是一种可重复使用的解决方案。我们用设计模式来解决面向对象设计时碰到的问题,而解决问题的目标就是让我们的设计满足面向对象的设计原则,以提升设计的可扩展性。
每一种设计模式都描述了一种问题的通用解决方案,且这种问题是反复地在我们的工作场景中出现的。
一个设计模式分为四个部分:
- 模式的名称:由少量的字组成的名称,有助于我们表达我们的设计;
- 待解决问题:描述了何时需要运用这种模式,以及运用模式的环境(上下文);
- 解决方案:描述了组成设计的元素(类和对象)、它们的关系、职责以及合作。但这种解决方案是抽象的,它不代表具体的实现;
- 结论:运用这种方案所带来的利弊。主要是指它对系统的弹性、扩展性和可移植性的影响;
这些设计模式按不同的分类方式可以分为不同的类型:
按功能来分可分为三类:
- 创建模式:对类的实例化过程的抽象;
- 结构模式:将类或者对象结合在一起形成更大的结构;
- 行为模式:对在不同的对象之间划分责任和算法的抽象化;
按方式来分可分为两类:
- 类模式:以继承的方式实现模式,静态的;
- 对象模式:以组合的方式实现模式,动态的;
单例模式
一般,我们写单例,最容易写的是饿汉模式:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
这种方式是没问题,但是挑剔的人会说,这里的INSTANCE
在初始化的时候初始化了,如果这样的类非常多,但是,只有少数几个才使用,那这里实例化的对象就白白的占用了很多的内存。
这个时候,一般会想到Lazy
了,也就是懒汉模式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这份代码看上去是懒汉模式,但是其实是有问题的,在多线程环境下,如果两个线程同时进入到if (instance == null)
这里之后,instance
会被初始化两次,两个线程拿到的不是同一个对象,这就不是单例了。
于是乎,首先想到的就是给getInstance()
方法加上synchronized
关键字:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这份代码虽然是解决了重复实例化的问题,但是同步的粒度比较大,并发线程比较多的时候,每个线程调用都要去做同步的操作,而单例模式的实例对象,一旦被实例化之后就不会再改变(除非重启应用),所以这个同步的粒度是可以优化的。
于是乎,就出现了双重检查的懒汉模式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
这里对instance
的初始化做了两次检查,所以叫双重检查。双重检查完美的避免了上面的问题,只要instance
被实例化了就不再走同步的代码块。
但是,上面的代码还是有问题的,问题点就在于instance
的实例化。
在Java
中,实例化一个对象分为三步:
- 分配内存空间;
- 初始化对象;
- 将内存空间的地址赋值给对应的引用;
然而,现实是操作系统可以对指令进行重排序,所以上面的步骤可能会变成132,而不是123。所以,双重检查的懒汉模式需要给instance
的定义处,加上volatile
关键字,这个关键字在这里的作用是:禁止指令重排序优化。换句话说,就是volatile
修饰的变量的赋值操作后面会有一个内存屏障,读操作不会被重排序到内存屏障之前。
所以,正确的代码应该是这样的:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
双重检查(据说需要使用JDK 1.5+
,在此之前JMM
模型存在缺陷,我没有验证过)完成了,但是还是会有同步的操作存在,也就是说会有锁,而无锁肯定会优于无锁的方式,那有没有一种无锁的方式来实现这个单例呢?
答案是有的。
我最初看到这种方式实现的单例是在apache commons utils
的一个类的源码中,第一次看到时我是真的惊讶到了,一个被大家写滥和鄙视到不行的单例模式,居然可以写得这么优雅。
具体代码如下,也是我交的作业1的答案:
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
这份代码没有锁,并且,LazyHolder
中的INSTANCE
实例在Singleton#getInstance()
方法调用之前是不会被实例化的,这就不会出现饿汉模式那种过早占用内存的情况;而且,上面的代码是使用静态嵌套类的方式实现的,所以,能绝对的保证INSTANCE
只会被实例化一次,因此,就不需要双重检查了。
至此,我个人觉得,最美的单例模式已经产生了~~