设计模式2-单例模式

定义

单例模式是一种创建对象的方式,这种模式涉及到一个单一的类,该类负责创建自己的对象。同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意点

1、单例类只能有一个实例
2、单例类必须自己创建自己的唯一实例
3、单例类必须给所有其他对象提供这一实例

实现方式及优缺点分析

1、懒汉单例(线程不安全式)

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

优点:是懒加载的一种体现方式
缺点:这种模式线程不安全。不安全的点在于其在多线程的情况,多线程同时请求会产生多个单例,严格意义上不算是单例模式

2、懒汉单例(线程安全式)

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

优点:第一次调用才初始化,避免内存浪费
缺点:必须要加锁才能保证单例,但是加锁会影响效率,造成性能开销,毕竟只会出现一次实例化单例对象,其余情况都是只是获取该对象,然而每次都是经过了锁

3、饿汉单例(线程安全)

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

饿汉单例通过classloader机制(后续补充classloader机制文章介绍)避免了多线程的同步问题。

优点:没有加锁,执行效率变高
缺点:类加载的时候就初始化,会浪费内存,由于会有一些其他方式比如说静态方法导致类加载,因此其并非是懒加载的模式。

4、双重校验锁(线程安全)

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

双重校验锁实现单例,重要的是volatile防止指令重排序功能。

关键代码在于 singleton = new Singleton();这段代码内部。

这段代码事实上走了三步

1
2
3
1,申请内存 memory = allocate()
2,初始化内存 ctorInstance(memory)
3,指针指向该内存 instance=memory

这三步会进行指令重排序(后续补充指令重排序的细节),可能会变成132,132在多线程会出现问题

a线程执行了13,事实上该对象已经生成,此时b线程判断if就会是true,直接返回了这个对象,此时该对象初始化事实上没完成。就出现了问题。

因此使用volatile对singleton进行修饰,禁止指令重排序

优点:使用时实例,多线程稳定,访问也很快
缺点:略微复杂,由于指令重排序的历史限制问题,该方法仅限于jdk1.5以上版本

5,静态内部类(懒汉线程安全)

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

这种方式和3一样使用到了classloader机制,但是差别是当Singleton被装载的时候,singleton并不会被实例。

主要是因为sinletonHolder没有被显式调用,仅仅当singletonHolder被显式调用的时候,才会实例化singleton。

优点:能够延迟初始化静态域,线程安全
缺点:仅仅只能延迟初始化静态域,但是无法延迟实例域

6,枚举

1
2
3
4
5
public enum Singleton {  
INSTANCE;
public void whateverMethod() {
}
}

优点:它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
缺点:用的比较少,无法通过反射来调用初始化方法。同时也需要jdk1.5以上

如果涉及到反序列化创建对象时,可以尝试使用枚举进行初始化创建。

多进程实现单例

首先不建议这么做,多进程不适合进行单例处理。

如果真的要这么做,也是有办法的,基于多进程的进程间交互手段,开辟通信区域,实时传递。但是很蛋疼。可以实现。

参考这篇文章https://www.jianshu.com/p/2e1f7004c106。整体看下来的确可行但是没有动手的欲望。

是真的比较蛋疼。