单例模式(SingletonPattern)

概念

单例设计模式(Singleton Design Pattern)。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式 。

实现一个单例需要考虑的问题?

  1. 构造函数需要时private访问权限的,这样才能避免外部通过new创建实例。
  2. 考虑对象创建时的线程安全问题。
  3. 考虑延迟加载的问题。
  4. 考虑性能问题,是否加锁。

实现方式

1.饿汉式

1
2
3
4
5
6
7
8
9
10
public class HungryType {
public static final HungryType instance = new HungryType();

private HungryType() {
}

public static HungryType getInstance() {
return instance;
}
}

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。 这个不支持懒加载不一定就不好,提前初始化确实是一种浪费资源的行为,但是如果你的这个单例类初始化时间过长的话,极端一点,还不如系统运行起来的时候就先加载好,这样用户访问响应变快,当然这个比较极端,空间换时间。

2.懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazyType {
public static LazyType instance = null;

private LazyType() {
}

public static synchronized LazyType getInstance() {
if (instance == null) {
instance = new LazyType();
}
return instance;
}
}

懒汉式相对饿汉式来说就是支持懒加载,但是这个synchronized关键字,每次去访问这个方法都会锁,导致这个函数并发度较低。我认为不要使用这种方法。

3.双重检测(DCL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DCLType {
public volatile static DCLType instance;

private DCLType() {
}

public static DCLType getInstance() {
if (instance == null) {
// 类级别的锁
synchronized (DCLType.class) {
if (instance == null) {
instance = new DCLType();
}
}
}
return instance;
}
}

双重检测锁(DCL)解决了懒汉式synchronized关键字每次访问该方法都加锁的问题。提高了性能。这个方式既支持懒加载又提高了并发度。

volatile

这个instance加这个关键字是因为指令重排的问题,防止对象被new出来,赋值给了instance但是还没来得及初始化,就被另一个线程拿去使用了。可加可不加,现在高版本的Java不存在这个问题了。(这个我只是简单的说一下,这个要了解JVM类加载的过程,字节码指令)

4.静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InnerClassType {

private InnerClassType() {
}

// 定义私有的静态内部类
private static class SingletonHolder {
private static final InnerClassType instance = new InnerClassType();
}

public static InnerClassType getInstance() {
return SingletonHolder.instance;
}
}

这个静态内部类的方法是简化了DCL方式,但是又拥有DCL的特性。我感觉这个不错!这种实现方法既保证了线程安全,又能做到延迟加载。

如果了解静态内部类特性的话,肯定知道外部类加载静态内部类不会去加载,等你调用getInstance这个方法的时候才会去加载静态内部类 。这就实现了懒加载

至于线程安全,这个JVM来保证的。

5.枚举

1
2
3
4
5
6
7
8
public enum EnumType {
INSTANCE;
private AtomicInteger count = new AtomicInteger();

public int getId() {
return count.incrementAndGet();
}
}

之所以存在一个枚举类是因为前面的几种严格意义上来说并不安全,虽然加了synchronized关键字,但是你这个只能保证多线程环境下的安全,Java中存在反射

枚举是绝对安全的,而且简单,要知道为什么就需要去详细了解enum。简单的说一下这个类反编译后是继承java.lang.Enum这个类的。所以说enum可以算是一种class的一种特殊化。

总结

我只是简单的列举了单例的几种实现方式,其中并没有写一些关于业务的逻辑代码,比如你可以像枚举类那个例子中那样,用单例模式实现一个简单的计数器。这些单例模式前三种肯定要十分明白,尤其是第三种(DCL)。但是我都感觉不太好,我感觉静态内部类较好,枚举类实现单例最好。

上述只是说明单例的实现的几种方式,并没有说明为什么要使用单例模式,单例模式的弊端(破坏面向对象的特性等等),替代单例模式的方案,多例模式(限定数量new多个一个类的实例)的实现方式,可以百度查找,这个我目前只是明白一些并不能总结很好,停留知道怎么使用阶段。