一、什么是单例模式?
单例模式(Singleton Pattern),顾名思义,就是被单例的对象只能有一个实例存在。单例模式的实现方式是,一个类能返回对象的一个引用(永远是同一个)和一个获得该唯一实例的方法(必须是静态方法)。通过单例模式,我们可以保证系统中只有一个实例,从而在某些特定的场合下达到节约或者控制系统资源的目的。
二、代码如何实现?
- 饿汉模式
最常见、最简单的单例模式写法之一,即在类的一开始就给它新建一个实例。示例如下:
//饿汉模式
class Singleton {
//一开始就创建一个实例
private static Singleton instance = new Singleton();
private Singleton() { }
//获取实例
public static Singleton getInstance() {
return instance;
}
}
存在的问题:这种方式有一个明显的缺点,那就是不管有没有调用过获得实例的方法,每次都会新建一个实例。
- 懒汉模式
饿汉模式的升级版本,即在类的一开始只创建一个引用,但并不实例化,只有在需要使用到它的时候,先去判断实例是否为空,如果为空的时候才会新建一个实例来使用。示例如下:
//懒汉模式
class Singleton {
//一开始并不创建实例
private static Singleton instance;
private Singleton() { }
//需要时再新建
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在的问题:该种模式存在一个严重的问题。那就是如果有多个线程并行调用getInstance()
的时候,还是会创建多个实例的,单例模式就失效了。
- 线程安全的懒汉模式
在懒汉模式的基础上,把它设为线程同步(synchronized)就好了。synchronize的作用就是保证在同一时刻最多只有一个线程运行,这样就避免了多线程带来的问题。示例代码如下:
//懒汉模式(线程安全)
class Singleton {
private static Singleton instance;
private Singleton() { }
//添加了 synchronized 关键字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在的问题:这种模式看似很好的解决了多线程的问题,但是它的效率并不高,每次调用获得实例的方法时都要进行同步,但是多数情况下并不需要同步操作。
- 双重检验锁
为了解决第三种模式的问题,延伸出来双重检验锁模式,即在获取实例的方法中,先去判断该实例是否为空,如果为空在再加同步锁,然后新建实例。示例如下:
//双重检验锁
class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
//第一个检验锁,如果不为空直接返回实例对象,为空才进入下一步
if (instance == null) {
//第二个检验锁,因为可能有多个线程进入到if语句内,所以加线程同步锁
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
存在的问题:该种模式此时并不是完美的。主要问题在在于instance = new Singleton();
这句代码,因为JVM(Java虚拟机)执行这句代码的时候,要做好几件事情,而JVM为了优化代码,有可能造成做这几件事情的执行顺序是不固定的,从而造成错误。
解决办法:为了解决上述问题,我们需要给实例加一个volatile关键字,它的作用就是防止编译器自行优化代码。此时,双重检验锁模式才完美实现。
private volatile static Singleton instance;
- 静态内部类
这种方式,利用了 JVM 自身的机制来保证线程安全,因为SingletonHolder
类是私有的,除了getInstance()
之外没有其它方式可以访问实例对象,而且只有在调用getInstance()
时才会去真正创建实例对象。示例如下:
//静态内部类
class Singleton {
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
private Singleton() { }
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
- 枚举
我们可以通过 Wife.INSTANCE 来访问实例对象,而且创建枚举默认就是线程安全的,还可以防止反序列化带来的问题。最为推荐。示例如下:
//利用枚举的方式创建单例
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}