单例模式

概念

在整个系统中只出现该类的一个对象。基本思路就是私有化其构造函数,在提供一个静态类的成员变量作为私有成员,提供一个类似getInstance()的方法来返回该对象,下面介绍实现单例模式的几种方式:

饿汉式

当类被加载的时候对象就被创造

1
2
3
4
5
6
7
8
9
class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singletion getInstance(){
return singleton;
}
}

这种饿汉式写法的优点就是不会有多线程的问题,确保对象只有一个;缺点就是无论是否使用该对象,都会被创建,不能够延迟加载,也不能够有效减少负荷。

懒汉式

针对上述缺点,提供了懒汉式的写法

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singletion getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}

这种写法能够达到延迟加载的效果,有效的减小了系统负荷,但是,如果是多线程的情况下,有可能会生成多个实例,因此不能保证单例,连最基本的需要都达不到怎么行?

synchronized改进1

在Java中,如果需要多线程同步,首先想到的就是synchronized关键字。那么如果我们加上synchronized如何?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singletion getInstance(){
if(singleton == null){
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}

上述代码在极端情况下仍会出现多例的情况,例如,两个线程同时进入了if(singleton == null)的块里,那么一个线程先创建对象,另一个也会再次创建。如果把synchronized (Singleton.class)写在外层,如:

1
2
3
4
5
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton();
}
}

这样虽然能够达到在多线程下确保单例,但效率非常低下,因为每次得到实例都要进行加锁,解锁的操作。

synchronized改进2

对于上面加synchronized的情况,似乎都不太好,因此我们考虑使用两次if(singleton == null)来实现确保单例的同时又保证效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singletion getInstance(){
if(singleton == null){
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}

上述代码基本能够满足懒汉式创建单例时,保证多线程下能够正常工作。这种方式也可以叫做双重检查“Double-Check”法

到此为止,我们觉得应该大功告成了吧,如果是C++(synchronized换成加锁,解锁的操作)那么应该就没问题了,但是在Java中,JVM会进行指令重排序,因此,对于一个简单的new, 如Singleton singleton = new Singleton(),它并不是原子的,它可以分为三个步骤:

  1. 分配内存地址
  2. 初始化成员变量
  3. singleton引用指向该地址

如果重排序的结果是1,3,2,,在线程执行完1,3后,被另一个线程抢占了,由于3执行后已经非null,线程2可以使用singleton,但实际上它并没有初始化(执行2),因此可能出错。在使用以上写法时,考虑使用关键字volatile禁止指令重排序.

两种更可靠的写法

静态内部类法

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static class Holder {
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}

即保证了效率有可以延迟加载,很好!!

但是!!,以上的方法有两个缺陷:1. 需要额外的工作来保证序列化,反序列化会创建新的对象;2. 可以通过反射机制来调用私有构造函数

枚举写法

终极写法!!《Efftive Java》推荐写法
优点:

  • 非常简单
  • 默认线程安全
  • 防止反射机制调用私有构造函数
  • 提供了自动序列化机制,防止反序列化的时候创建新的对象
1
2
3
4
5
6
7
8
9
10
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}

或者更优雅

1
2
3
4
5
6
7
public enum Singleton{
INSTANCE;
}
public static voi main(){
Singleton s = Singleton.INSTANCE;
}

默认枚举实例的创建是线程安全的,但是在枚举中的其他任何方法由程序员自己负责。