单例模式,也就是创建全局唯一的对象的模式,属于创建型模式。一个类只允许创建一个对象的时候,那这个类就是单例类。
单例类有很多种实现方式,例如饿汉式,懒汉式,双重检测,当然还有Sping基于IOC的单例模式等。下面就来说一下单例模式的用法,单例模式无外乎就几个关键的地方:
- 构造器是私有的
- 是否是线程安全的
- 是否支持延迟构造
1、饿汉式
public class SingleDog {
private static final SingleDog singleDog = new SingleDog();
private SingleDog(){}
public static SingleDog getSingleDog(){
return singleDog;
}
}
饿汉式的特点是,在类加载的时候就把对象创建好了。这里说明了两个问题,
- 没有线程安全问题。
- 不支持延迟加载。
有人会觉得不支持延迟加载是一个缺点,觉得可能会浪费资源。但是他有两个很重要的优点,第一点是节约调用时间,第一次调用的时候,如果初始化很长的话,可能会造成接口响应时间变长,甚至是超时。如果把初始化提前到启动的时候做的话,就不会出现这个问题了。
第二点是把异常提前到启动的时候,这个类在代码重是我们一定要创建成功的,例如ID获取器,例如Service类那些,如果没创建成功,系统就没办法使用了。这种我们就需要放在启动的时候做了。
2、懒汉式
和饿汉式相对的就是懒汉式了,先来看看实现方式:
public class SingleDog {
private static SingleDog singleDog;
private SingleDog(){}
public synchronized static SingleDog getSingleDog(){
if(singleDog == null){
singleDog = new SingleDog();
}
return singleDog;
}
}
特点就是支持延迟加载。缺点很明显,因为加了重量级锁,导致并发度很低。如果访问量不大,那还可以,如果访问量大的话,这种调用方式是不可以接受的,严重影响了性能。
3、双重检查
饿汉式不支持延迟加载,懒汉式有并发问题。接下来就介绍一种既支持延迟加载,又支持高并发的写法。双重检查,只有没创建的对象时候会加锁,有对象的时候访问不会加锁,大大减少了加锁次数。
public class SingleDog {
private volatile static SingleDog singleDog;
private SingleDog(){}
public static SingleDog getSingleDog(){
if(singleDog == null){
synchronized (SingleDog.class){
// 双重检查,避免锁住的时候被分配对象了
if(singleDog == null) {
singleDog = new SingleDog();
}
}
}
return singleDog;
}
}
4、静态内部类
这种方式的效果类似于双重检查,但是实现起来又比双重检查实现起来简单。充分利用了JVM的特性,JVM在初始化外部类的时候,并不会初始化内部类,在调用getInstance()的时候才会初始化内部类(可以通过静态代码块去试验一下就知道了)。
public class SingleDog {
private SingleDog(){}
private static class InnerSingleDog{
private static final SingleDog singleDog = new SingleDog();
}
public static SingleDog getInstance(){
return InnerSingleDog.singleDog;
}
}
5、Spring
如果是使用Java的朋友,在工作中使用最多的应该就是Spring的单例模式了。因为构造器是public的,这种单例是需要程序员去保证单例的,获取实例的时候通过Spring的IOC去获取。
@Bean
public SingleDog singelDog(){
return new SingeleDog();
}