单例模式目的是在一个进程中只有一个实例,具体实现是将构造方法私有化,在类的内部使用一个静态字段来引用唯一创建的实例

单例模式分为以下几种模式

饿汉式:

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

private Singleton () {}

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

缺点:

单例对象的创建不是延迟加载

优点:

线程安全,不需要加锁

懒汉式

懒汉式为了支持延迟加载,将对象的创建延迟到了获得对象的时候。但是为了线程安全不得不为了获取对象的操作加锁,这降低了性能。并且这个锁只有第一次创建对象时候有用,之后就是累赘
1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton { 
private static final Singleton instance;

private Singleton () {}

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

优点

创建对象的过程是线程安全的,并且支持延迟加载

缺点

加锁影响了性能

双重检测

双重检测解决了懒汉式和饿汉式的缺点,就是将原有的锁住整个方法改成synchronized代码块,同时使用volatile来禁止重排序,保证了多线程下的线程安全

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

private Singleton () {}

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) { // 这里的检测避免多线程并发时多次创建对象
instance = new Singleton();
}
}
}
return instance;
}
}

如果不加volatile的话,在第二个判空的位置可能有问题

因为在instance不为空时候,可能此时这个对象还没有完成初始化

创建对象这个过程,大致可以分为三步

1、分配对象的内存空间

2、初始化对象

3、设置instance指向刚分配的内存地址

第二步和第三步可能会发生重排序,但是单线程会遵循intra-thread semantics 即保证重排序不会改变单线程的程序执行结果。但是在多线程的情况下,另一个线程可能看到一个未初始化的对象,这时候直接返回会出错。

当变量声明为volatile后,多线程环境中的第二步和第三步重排序会被禁止。

优缺点

优点

  • 保证一个类只有一个实例
  • 获得了一个指向该实例的全局访问节点
  • 仅在首次请求单例对象时候完成初始化

缺点

  • 违反了单一职责原则,同时解决了两个问题
  • 多线程环境需要额外处理