【设计模式】单例模式

        在GoF的23种设计模式中,单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。这里主要介绍5种写法。

饿汉式

        以下写法,是基于classloader机制,避免了多线程的同步问题,但在加载时没有达到lazy loading的效果。

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

懒汉式-非线程安全

        以下写法,lazy loading很明显,但是致命的是在多线程不能正常工作。

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

懒汉式-线程安全

        这种写法能在多线程中很好的工作,而且具备lazy loading,但同步方法效率很低。

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;
}
}

double check

        这种写法是比较推崇的一种写法。加锁双重检查对效率也有一定影响,不过影响不大。流程分析:为了防止new Singleton被执行多次,因此在new操作之前加Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建对象。

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

静态内部类

        这种方式:InnerSingleton类被装载了,Singletion不一定被初始化。因为Singletion类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类。有效的实现了 lazy loading,同时也避免加锁。本质上利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

1
2
3
4
5
6
7
8
public class InnerSingleton {
private static class Singletion {
private static Singletion single = new Singletion();
}
public static Singletion getInstance(){
return Singletion.single;
}
}

        最终问题:静态内部类的实现方式虽好,但是存在着单例模式共同的问题:无法防止利用反射来重复构建对象。

枚举实现

        有了enum语法糖,JVM会组织反射获取枚举类的私有构造方法。使用枚举类实现的单例模式不仅能够防止反射构造对象,而且可以保证线程安全。
        但是这种方式也有唯一的缺点,就是它并非使用懒加载,其单例对象实在枚举类被加载的时候进行初始化的。

1
2
3
public enum SingletonEnum {
INSTANCE;
}

简单总结

image.png

注意注意

  • volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,我在以后的漫画中会专门讲解。
  • 使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。
  • 对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。
文章目录
  1. 1. 饿汉式
  2. 2. 懒汉式-非线程安全
  3. 3. 懒汉式-线程安全
  4. 4. double check
  5. 5. 静态内部类
  6. 6. 枚举实现
  7. 7. 简单总结
|