单例模式,指的是采取一定的方法,保证在整个软件系统中,对某个类来说,它只存在一个实例对象。
说起单例模式,我们一般都能说出饿汉式和懒汉式。本文提供具体的六个单例模式的实现方式,并讨论它们的优缺点。
一、饿汉式
首先,最简单的实现方式就是饿汉式。
- 在类中定义私有静态属性,并且进行初始化
- 构造器私有化
- 提供一个静态公共方法,在需要时能获得该实例对象。
根据初始化的位置不同,又可以分为两种写法:显式初始化和静态代码块初始化。
饿汉式在类加载的时候就完成了实例化,避免了线程同步的问题,但是也没有达到懒加载的效果,因此如果自始至终没有用到过这个实例,有内存浪费的可能。
public class Singleton {
//私有静态属性,显式初始化
private final static Singleton instance = new Singleton();
//构造器私有化
private Singleton() {}
//向外提供一个静态的公共方法,返回实例对象instance
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
//私有静态对象
private final static Singleton instance;
//构造器私有化
private Singleton() {}
//静态代码块初始化
static {
instance = new Singleton();
}
//向外提供一个静态的公共方法,返回实例对象instance
public static Singleton getInstance() {
return instance;
}
}
二、懒汉式
为了实现懒加载,我们来看看懒汉式。懒汉式与饿汉式的不同在于给私有静态属性初始化的时机不同。我们选择在需要的时候再进行初始化,得到实例对象。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
//①
instance = new Singleton();
}
return instance;
}
}
上述写法实现了懒加载,但是又存在线程不安全的问题。当多个线程都进入①,会创建多个实例,不满足单例模式的要求。因此,我们做出改进如下
同步方法:将获取实例的方法定义为synchronized。这样虽然解决了线程安全问题,但是当多个线程想要获得实例的时候都要进行同步,即使实例已存在。我们希望当实例已经被创建后,其他线程访问时可以直接获得而不要全部等待,我们希望提高效率。
public class Singleton {
private static Singleton instance;
private Singleton() {}
//静态方法:同步监视器是类本身
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查+同步代码块法:线程想要获取实例对象时,先判断其是否已存在,若存在,直接返回;若不存在,试图创建实例对象,即进入①。此时若有多个线程进行同步,在同步代码块内还需再进行一次判断,判断实例是否已被创建,这就保证了自始至终无论存在多少线程想要创建实例对象,都只会创建一次。因此,该方法既保证了线程安全,又实现了加载,而且效率高。
public class Singleton {
//volatile:让修改值立即更新到内存
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance==null){
//①
synchronized (Singleton.class){
if (instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
三、静态内部类法
唯一的实例对象作为内部类的私有静态属性而存在。当加载外部类的时候,内部类不会加载,以实现懒加载;需要获取实例的时候,加载内部类,此时利用JVM底层类加载机制,保证了内部类成员变量初始化的线程安全。
public class Singleton {
private Singleton() {}
//1. 加载外部类的时候,内部类不会加载,实现懒加载
private static class SingletonInstance {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
//2. 此时加载内部类,利用JVM的底层装在机制:在加载类的时候是线程安全的
return SingletonInstance.INSTANCE;
}
}
四、枚举类法
避免线程同步问题,防止反序列化重新创建新的对象
public enum Singleton {
INSTANCE;
}
优缺点对比
1. 饿汉式:显式初始化、静态代码块初始化
线程安全,但是没有实现懒加载。
2. 懒汉式
同步方法
线程安全,实现懒加载,但是效率低。
双重检查+同步代码块法
线程安全,实现懒加载,效率高。
3. 静态内部类法
线程安全,懒加载。
4. 枚举类法
线程安全,防止反序列化重新创建新的对象
总结
推荐使用
- 懒汉式中双重检查+同步代码块法
- 静态内部类法