设计模式:单例模式 的四种创建方式和一些思考

2018-03-08  本文已影响0人  Marker_Sky

单例模式的含义:

程序运行过程中,希望某个实体(对象)在内存中只有一个实例。

使用条件:

比如使用 Glide 加载图片,获取 Glide 对象的过程就是一个单例模式。因为 Glide 对象的创建过程相当复杂,而且在使用的过程中只需要一个可用 Glide 对象。

public static Glide get(Context context) {
    if(glide == null) {
        Class var1 = Glide.class;
        synchronized(Glide.class) {
            if(glide == null) {
                Context applicationContext = context.getApplicationContext();
                List modules = (new ManifestParser(applicationContext)).parse();
                GlideBuilder builder = new GlideBuilder(applicationContext);
                ...
                glide = builder.createGlide();
                ...
            }
        }
    }
    return glide;
}

单例模式的创建:

一、饿汉式


一般来说创建对象首先想到构造函数,那么单例的创建可用先从某对象的构造函数说起。

  1. 首先来说因为该类的单例对象只创建一次,可用考虑使用 static 修饰,这样在 JVM 加载该类的时候就会自动创建对象;
  2. 接下来我们不希望其他类执行该单例类的构造方法再去创建单例对象,所以把构造函数的属性设置为 private;(感叹 Java 的封装设计)
  3. 那么存在单例对象就需要把这个对象暴露出去,通过 public 的方法无疑是一种比较好的方式。
    这里为什么不直接把单例设置为 public 呢?这里就联系到设计原则:迪米特原则了,其他类对该单例类了解的尽量少。其他类获取该单例类的对象只需要通过其方法暴露出来即可,而不需要了解单例具体是怎么创建的。假如该单例类创建的过程变得更加复杂,其他类的调用还是通过这个简单的方法获得对象而不用关心单例类增加了哪些代码。
public class Singleton {

    // 私有单例变量,在 JVM 加载时就会自动创建
    private static Singleton INSTANCE = new Singleton();

    // 创建私有构造方法,避免其他类再创建对象
    private Singleton(){
    }
    
    // 向其他类暴露获取实例的方法
    public static Singleton newInstance(){
        return INSTANCE;
    } 

}

那么这种创建方式就是 饿汉式,这种创建方式特点:

二、懒汉式


1. 懒汉式(普通使用)

或许我们需要更加灵活地创建和销毁某个单例对象,又或许需要使用的单例对象比较复杂占用内存比较多,所以需要一种灵活的按需创建的方式:

  1. 与之前的逻辑类似,依旧不想被其他类通过构造方法或直接获取单例对象,所以构造函数和单例对象应该是 private 的;
  2. 需要一个方法把单例对象暴露出去,这个方法中灵活创建单例对象。
public class Singleton {

    // 定义私有变量并设置初始为 null
    private static Singleton INSTANCE = null;

    // 创建私有构造方法,避免其他类再创建对象
    private Singleton(){
    }

    // 向其他类暴露获取实例的方法,需要时再创建
    public static Singleton newInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

}

这种创建方式就是懒汉式,但是这种方式存在明显的问题,就是线程安全问题:

2. 懒汉式(同步锁)

既然这种方式有缺陷而且是线程安全问题,那么就加个同步锁 synchronized 来访问创建单例对象的部分:

public class Singleton {

    // 定义私有变量并设置初始为 null
    private static Singleton INSTANCE = null;

    // 创建私有构造方法,避免其他类再创建对象
    private Singleton(){
    }

    // 加入同步锁,确保同一时刻只有一个线程访问该方法
    public static synchronized Singleton newInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    
    // *第二种写法
    =========================================================
    public static Singleton newInstance(){
        if (INSTANCE == null){
            synchronized (Singleton.class){
                INSTANCE = new Singleton();
            }
        }
        return INSTANCE;
    }

}
3. 懒汉式(双重校验)
public class Singleton {

    // 使用 volatile 关键字
    private static volatile Singleton INSTANCE = null;

    // 创建私有构造方法,避免其他类再创建对象
    private Singleton(){
    }

    public static Singleton newInstance(){
        // 第一重判断
        if (INSTANCE == null){
            synchronized (Singleton.class){
                // 第二重判断
                if(INSTANCE == null){
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }

}

三、静态内部类


利用 Java 静态类的特点来创建单例对象。

public class Singleton {

    private static class SingletonStatic{
        // 在静态内部类中加载外部类静态对象
        private static Singleton INSTANCE = new Singleton();
    }
    // 私有构造器
    private Singleton(){
    }
    // 获取静态内部类实例化的对象
    public static Singleton newInstance(){
        return SingletonStatic.INSTANCE;
    }
}

上面代码可以看出:

  1. 使用静态内部类来创建外部类静态对;
  2. 使用 newInstance() 时才会加载静态内部类,创建单例对象并返回;
  3. 该静态内部类只会被 JVM 加载一次,利用这个特性解决线程同步问题。

四、枚举方式创建单例


public enum  Singleton {

    INSTANCE;

    // 隐藏默认的空的私有构造方法
    // private Singleton(){}
}

// 使用
Singleton singleton = Singleton.INSTANCE;

这种创建方式利用枚举的特性保证了 按需加载、线程同步

参考资料:

单例模式(Singleton) - 最易懂的设计模式解析
从Android代码中来记忆23种设计模式

上一篇下一篇

猜你喜欢

热点阅读