设计模式--01.单例模式

在程序设计中,需要保证一个只有一个对象实例,就是所谓的单例模式。在java中,有很多单例模式的实现,这篇博客是对这几种单例模式的优缺点进行分析和优化:

饿汉模式


所谓饿汗模式,就是优先创建对象,对象在类加载的时候就已经创建好了,具体的代码如下:

1
2
3
4
5
private static Singleton01 instance = new Singleton01();

public static Singleton01 getInstance() {
return instance;
}

饿汗模式优点是代码简单,由于是在类加载的时候就已经创建好了对象,所以不存在线程安全的问题。

缺点是:有的类在没有使用的时候就已经创建了对象,产生了多余的垃圾对象。


懒汉模式


懒汉模式是针对饿汉模式的一个优化,类只有在使用的时候才会进行实例化:

1
2
3
4
5
6
7
8
9
10
   private static Singleton02 instance;
//懒汉模式
//简单,但是线程不安全
public static Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}

懒汉模式的实例在使用的时候才去创建,但是创建的过程是线程不安全的,在多线程的环境下存在多个实例的现象。


double check模式


doublie check模式优化了懒汉模式的线程安全的问题,使用锁来保证当前只有一个线程创建实例。

1
2
3
4
5
6
7
8
9
10
  private static Singleton03 singleton03;

//加锁,线程安全,但是效率慢
public synchronized static Singleton03 getInstance() {
if (singleton03 == null) {
singleton03 = new Singleton03();
}
return singleton03;
}

上面的代码足够保证了线程的安全,但是多线程的场景下会存在一个线程工作,其他线程盲等的现象,所以再为了优化这个问题,再创建进入锁之前再次进行检查, 使用volatile关键字保证变量在多线程中是共享的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private volatile static Singleton05 singleton05;
//double check 性能最好
public static Singleton05 getInstance() {
if (singleton05 == null) {
synchronized (Singleton05.class) {
if (singleton05 == null) {
singleton05 = new Singleton05();
}
}
}
return singleton05;
}

虽然doublecheck的模式,继承了懒汉模式的优点,也不会有线程安全的问题,但还是会存在线程盲等的现象,优化只能降低发生的概率,无法完全避免。


内部类模式


内部类模式是一个比较精妙的设计,是利用了Jvm的类的加载的特点,具体代码如下:

1
2
3
4
5
6
7
8
9

public static Singleton06 getInstance() {
return Inner.INSTANCE;
}

static class Inner {
final static Singleton06 INSTANCE = new Singleton06();
}

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式。
缺点是会产生多余的类,反射也可以破解,通过构造函数可以防止破解


枚举单例模式


枚举是《effective java》中推荐的单例模式,利用jvm的枚举类无法被反射的创建实例的特点,所以枚举类相对更加安全和健壮。具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
public enum Singleton07 {
INSTANCE;
Singleton07() {
System.out.println(this.getClass().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

虽然枚举模式有点很多,但是再实际开发中却用的很少,原因是枚举类的用法比较单一(不支持继承)。


使用memoize创建单例


使用Guavamemoize函数的特性, 只再第一此获取的时候才调用,可以用来实现单例模式,代码如下:

1
2
3
4
5
6
private static final Supplier<Singleton08> InstanceSuppler = Suppliers.memoize(Singleton08::new);

public static Singleton08 getInstance() {
return InstanceSuppler.get();
}

使用memoize函数,可以支持多种的实例函数的调用方式,也可以制定方法的过期的时间,更为灵活,个人推荐使用memoize方式