常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:
1、构造器需要私有化
2、暴露一个公共的获取单例对象的接口
3、是否支持懒加载(延迟加载)
4、是否线程安全
1、饿汉式
饿汉式的实现方式比较简单。在类加载的时候,instance 静态实例就已经创建并初
始化好了,所以,instance 实例的创建过程是线程安全的。从名字中我们也可以看
出这一点。具体的代码实现如下所示:
public class EagerSingleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
事实上,恶汉式的写法在工作中反而应该被提倡,面试中不问,只是应为他简单。很
多人觉得饿汉式不能支持懒加载,即使不使用也会浪费资源,一方面是内存资源,一
方面会增加初始化的开销。
1、现代计算机不缺这一个对象的内存。
2、如果一个实例初始化的过程复杂那更加应该放在启动时处理,避免卡顿或者构造
问题发生在运行时。满足 fail-fast 的设计原则。
2、懒汉式
有饿汉式,对应地,就有懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载,具体
的代码实现如下所示:
public class LazySingleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
以上的写法本质上是有问题,当面对大量并发请求时,其实是无法保证其单例的特点
的,很有可能会有超过一个线程同时执行了new Singleton();
当然解决他的方案也很简单,加锁呗:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这样的话当我们去面对着大量的请求的时候,这个加锁之后,我们所形成的相关的请求就会分解开来,这些请求也会去排成一队,分别的去进行相关的处理
以上的写法确实可以保证jvm中有且仅有一个单例实例存在,但是方法上加锁会极大
的降低获取单例对象的并发度。同一时间只有一个线程可以获取单例对象,为了解决
以上的方案则有了第三种写法。
(这个地方当我们使用了序列化进行加锁的相关的操作的时候,这个时候就违背了我们在使用Java中的并发编程的优良的相关的特点,这样就会是个我们处理相关的应用的速度极大的减慢)
3、双重检查锁
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支
持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式:
在这种实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不
会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。具体
的代码实现如下所示:
public class DclSingleton {
// volatile如果不加可能会出现半初始化的对象
// 现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很
简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序),
为了兼容性我们加上
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
4、静态内部类
我们再来看一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。
它有点类似饿汉式,但又能做到了延迟加载。具
public enum EnumSingleton {
INSTANCE;
}
体是怎么做到的呢?我们先来看它的
代码实现。
public class InnerSingleton {
/** 私有化构造器 */
private Singleton() {
}
/** 对外提供公共的访问方法 */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/** 写一个静态内部类,里面实例化外部类 */
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
SingletonHolder 是一个静态内部类,当外部类 Singleton被加载的时候,并不会创
建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,
SingletonHolder 才会被加载,这个时候才会创建 instance。insance 的唯一性、创
建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,
又能做到延迟加载。
5、枚举
最后,我们介绍一种最简单的实现方式,基于枚举类型的单例实现。这种实现方式通
过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。具体
的代码如下所示:
这是一个最简单的实现,因为枚举类中,每一个枚举项本身就是一个单例的:
public enum EnumSingleton {
INSTANCE;
}
事实上我们还可以将单例项作为枚举的成员变量,我们的累加器可以这样编写:
}
}
public enum EnumSingleton {
INSTANCE;
}
public class EnumSingleton {
private Singleton(){
}
public static enum SingletonEnum {
EnumSingleton;
private EnumSingleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public EnumSingleton getInstance(){
return instance;
}
}
}
public enum GlobalCounter {
INSTANCE;
private AtomicLong atomicLong = new AtomicLong(0);
public long getNumber() {
return atomicLong.incrementAndGet();
}
}