单例模式的本质就是只提供一个实例,Java中单例模式主要有以下5种实现方式:
饿汉式
实现步骤:
- 定义SingletonHurry类
- 私有化构造函数
- 定义SingletonHurry类型的静态变量instance,并初始化
- 定义静态方法getInstance(),返回instance
参考代码如下:
//饿汉式,加载类的时候马上创建对象,没有线程安全问题
public class SingletonHurry {
private static SingletonHurry instance = new SingletonHurry();
private SingletonHurry(){}
public static SingletonHurry getInstance(){
return instance;
}
}
可以看出,饿汉式由于类初始化时就造出了单例对象,所以没有线程安全问题,但是无法实现延迟创建单例对象
懒汉式
实现步骤:
- 定义SingletonLazy类
- 私有化构造函数
- 定义SingletonLazy类型的静态变量instance,但不初始化
- 定义静态方法getInstance(),加同步锁,判断instance对象是否已创建,如果未创建则造对象并返回,如果已创建则直接返回创建好的对象
参考代码如下:
//懒汉式,等要用的时候再创建对象,为了线程安全需要判断锁,故效率较低
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy(){}
public static SingletonLazy getInstance(){
//同步锁
synchronized (SingletonLazy.class)
{
//判断单例对象是否已创建
if (instance == null){
instance = new SingletonLazy();
}
}
return instance;
}
}
可以看出懒汉式可以实现延迟创建单例对象,但是为了解决线程安全问题必须加同步锁,导致效率较低
双重校验锁
该方式是对懒汉式的一种优化,提升效率,实现步骤如下:
- 定义SingletonLazyDoubleCheckLock类
- 私有化构造函数
- 定义SingletonLazyDoubleCheckLock类型的静态变量instance,但不初始化
- 定义静态方法getInstance(),方法中先判断一下instance对象是否已创建,如果已创建直接返回,不进入加同步锁的环节;如果未创建,则进入同步锁,判断instance对象是否已创建,如果未创建则造对象并返回,如果已创建则直接返回创建好的对象
参考代码如下:
public class SingletonLazyDoubleCheckLock {
private static SingletonLazyDoubleCheckLock instance;
private SingletonLazyDoubleCheckLock(){}
public static SingletonLazyDoubleCheckLock getInstance(){
//一上来先判断对象是否创建,如果已创建就跳过判断锁的步骤,提升效率
if (instance == null){
synchronized (SingletonLazyDoubleCheckLock.class){
if (instance == null){
instance = new SingletonLazyDoubleCheckLock();
}
}
}
return instance;
}
}
由于懒汉式效率低的主要原因是每次调用getInstance方法都要进入同步锁的判断,比较耗时,实际上只要单例对象创建好以后,后续的线程调用getInstance方法时直接返回instance即可,无需每次都判断同步锁,故双重校验锁模式的优化点也在此,在getInstance方法中首先判断一下instance对象是否已创建,如果已创建就无需再进入同步锁判断环节,直接返回
静态内部类
实现步骤:
- 定义SingletonStaticInnerClass类
- 私有化构造函数
- 定义静态内部类StaticInnerClass,包含一个SingletonStaticInnerClass类型的静态变量instance,并初始化
- 在外部类中定义静态方法getInstance(),返回静态内部类的静态变量instance
参考代码如下:
public class SingletonStaticInnerClass {
private SingletonStaticInnerClass(){}
//定义静态内部类,包含一个静态的单例对象
private static class StaticInnerClass{
private static SingletonStaticInnerClass instance = new SingletonStaticInnerClass();
}
public static SingletonStaticInnerClass getInstance(){
//调用此方法时加载静态内部类(加载时能保证线程安全),初始化单例对象,实现延迟加载
return StaticInnerClass.instance;
}
}
这种方式比较巧妙,在调用getInstance()方法时首次使用了静态内部类,故导致静态内部类的加载与初始化,并利用jvm在类初始化时能确保线程安全这个特性,既实现延迟创建单例对象,又保证了线程安全
枚举
实现步骤:
- 定义枚举类SingletonEnum
- 枚举类中定义唯一一个实例INSTANCE
参考代码如下:
public enum SingletonEnum {
INSTANCE
}
可以看出这种方式代码特别简洁,利用了枚举类的特性,保证了线程安全,但是无法实现延迟创建单例对象
5种方式效率测试
测试方法:连续获取1亿次单例对象,分别记录每种方式花费的时间,取3次测试平均值,结果如下
可以看出除了懒汉式效率较低,其他4种方式效率都比较高,其中双重校验锁和静态内部类方式还具备可以延迟创建单例对象的优势
完整代码参考:https://github.com/baobao555/JavaFoundation/tree/master/DesignMode/src/designmode/singleton