设计模式之单例模式

Posted by ShiYu on 2018-03-19

应用场景

单例模式能保证一个类仅有一个实例,并提供一个访问它的全局访问点,主要用来解决一个全局使用的类频繁地创建与销毁问题,经典的应用场景有文件操作、数据库操作等。

实现

懒汉式

1
2
3
4
5
6
7
8
9
10
11
public class Singleton{
private static Singleton ins;
private Singleton(){}

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

该种实现方式为了实现线程安全,使用了synchronized关键字进行加锁,导致效率很低,99.9%的情况下不需要同步,但是getInstance()方法使用一般来说不太频繁,勉强可以接受。

饿汉式

1
2
3
4
5
6
7
8
public class Singleton{
private static Singleton ins=new Singleton();
private Singleton(){}

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

该种方法基于classloader机制避免了多线程同步问题,但是在ins在类装载时就实例化,相比较懒汉式比较浪费内存。

双检锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class Singleton{
private volatile static Singleton ins;
private Singleton(){}

public static getInstance(){
if(null==ins){
sychronized (Singleton.class){
if(null==ins){
ins=new Singleton();
}
}
}

return ins;
}
}

这种方式使用了双锁机制,保证了线程安全,且实现了懒加载,性能较高。大家应该注意到ins用volatile关键字进行了修饰,那么为什么要用volatile来修饰呢?主要原因在于 new Singleton()这句,这句并不是原子操作,在JVM中这句话大概做了下面3件事:

  1. 给ins分配内存;
  2. 调用Singleton的构造函数来初始化成员变量;
  3. 将ins对象指向分配的内存空间(该步骤完成ins就是非null了)

但是JVM的即时编译器中存在指令重排序优化,如果不使用Volatile关键字进行修饰,就不能保证指令的有序性,最终的执行顺序可能是1-2-3也可能是1-3-2。如果是后者,假如在3执行完毕、2未执行前,被另一个线程抢占了cpu资源,这是ins已经是非null了(并没有执行构造函数进行初始化),该线程会直接返回一个没有被初始化的ins并使用,于是顺利成章地报错了。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
private Singleton(){

}

public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

这种方式能实现和双检锁一样的功效,同时实现更简单,但是该种方法只能在静态域情况下使用,不能用在实例域中。

枚举

1
2
3
4
5
6
7
8
9
10

public enum MySingleton{
INSTANCE;

public void whateverMethod(){

}


}

枚举模式实现单例是最简单的,且其拥有 保证了线程安全、不会因为序列化产生新实例和防止反射攻击 三个优势。