单例模式是三种创建型设计模式之一,基本是设计模式里面最简单的一种了,面试时一般被问到在项目里用过什么设计模式,基本都会说到单例。
单例模式的定义是一个类只会创建一个实例,那我们就可以将其设计为单例。
单例的实现也很简单,只要思考以下几个条件即可:
- 构造函数设为private,让外部不能用new关键字创建实例
- 创建时是否线程安全
- 创建时是否允许懒加载
- 获取实例时是否高效(getIntance()方法是否有性能问题)
然后我们就上面几个问题开始思考,整个推论过程也就有了饿汉式,懒汉式,双从检测等方式。
饿汉式
首先我们很容易想到饿汉式,直接在一个类里面定义初始化好的一个静态成员变量,使用时直接返回即可。
public class SingletonDemo {
private final static SingletonDemo instance = new SingletonDemo();
private SingletonDemo() {
}
public static SingletonDemo GetInstance() {
return instance;
}
}
但是饿汉式有个问题如果实例很大,但一开始又用不到则没必要程序启动时候就创建,所以一般是做成懒加载(在需要用到的时候在创建)形式。
懒汉式
于是就有了下面的懒汉式。
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo() {
}
public static synchronized SingletonDemo GetInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}
}
懒汉式问题是用了一把大锁将整个方法锁住,保持线程安全,思路没问题,但是实现不太好,因为锁的粒度太大了,会造成很多不必要的等待,这种写法即使是已经创建了实例,多个线程访问还是要排队,这明显是不可接受的。所以我们需要对锁进行优化。
双重检测
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo() {
}
public static SingletonDemo GetInstance() {
if (instance == null) {
synchronized (SingletonDemo.class){
if (instance==null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
上面这块代码一开始看可能会觉得很奇怪,为啥没事去判断两次?感觉毫无意义。
但是当你想通了其实觉得还是有必要的,第二次判空主要是避免重复创建。
第一次判空是正常判空,这时候锁住类,排队的多个线程会有一个进入同步块,这时候实例为空,开始创建实例。当创建完实例后,线程会释放锁,下一个线程会进去,这时候如果不判断instance为空,那另一个线程会重新创建实例覆盖上一个实例。这会造成实例重复创建,违背了单例的初衷,另一个如果静态代码块如果使用的是this而不是类锁就会完全锁不住,因为实例不一样了。
双重检测虽然解决了多线程的冲突,但是有个很大问题是代码可读性很差,正常人一眼根本看不懂,是否有比较优雅的实现呢?
当然,请往下看。
静态内部类
public class SingletonDemo {
private SingletonDemo() {
}
private static class SingletonDemoHolder{
private static final SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo GetInstance() {
return SingletonDemoHolder.instance;
}
}
通过在单例类里面创建一个静态内部类,利用jvm的类加载特性,当外面的SingletonDemo被创建时并不会加载里面的SingletonDemoHolder类,只有当GetInstance()方法被调用时才会被加载,并创建instance实例。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
当然,还有另一种比较简单的实现,枚举。
枚举
public enum SingletonDemo1 {
INTANCE;
}
枚举是effective java比较推荐的一种单例实现方式,但是枚举这个我用的比较少,不做过多解释,有兴趣的朋友可以自行查看。
类比
我们知道IOC容器有自动创建bean的功能,里面有个字段scope代表创建类型,默认的是singleton,这块其实就是采用了单例的思想。但需要注意如果每次要新建bean的话需要将scope值改为prototype。