GOF —— 单例模式

本贴最后更新于 1520 天前,其中的信息可能已经时移世改

单例模式(Singleton)

单例模式(SIngleton)的目的是为了保证一个进程中,某个类有且仅有一个实例。

因为这个类只有一个实例,因此,该类不能允许 new 的方式创建实例。

  • 单例的构造方法必须是 private,这样就防止了调用方自己创建实例。
  • 既然不能通过 new 创建实例,只能通过访问静态成员变量的方式了,而且成员变量不能定义成 public(调用者可以直接通过 Singleton.instance 的方式更改 ),所以需要提供一个 获取静态成员变量的 静态方法。

饿汉式

/**
 *  饿汉式
 *  类加载到内存后,就实例化一个单例,JVM 保证线程安全
 *  简单实用,推荐使用!
 *  唯一缺点:不管用到与否,类加载时就完成实例化
 *  (话说你不用的,你装在它干啥?)
 */
public class Singleton01 {

    private static final Singleton01 INSTANCE = new Singleton01();

    private Singleton01() {
    }

    public static Singleton01 getInstance(){
        return INSTANCE;
    }

}

懒汉式

饿汉式虽然很简洁、而且有 JVM 线程安全,但是 饿汉式,不管用没用到这个实例,都会进行初始化。

为了达到按需初始化的目的,人们对饿汉式进行了更改。

线程不安全的懒汉式

/**
 * lazy loading
 *  也称懒汉式
 *  虽然达到了按需初始化的目的,但却带来了线程不安全的问题
 */
public class Singleton02 {

    private static Singleton02 INSTANCE ;

    private Singleton02(){}

    public static Singleton02 getInstance(){
        if(INSTANCE == null){                // ①
            INSTANCE = new Singleton02();    // ②
        }
        return INSTANCE;
    }

}

为啥线程不安全?

假设有两个线程同时调用 getInstance() 方法,当线程 1 走到上面代码 ① 处时,判断 INSTANCE 变量为空,走到了 if 方法里面,这时候 线程 2 刚好在 线程 1 执行 代码 ② 之前 走到了 代码 ① 处,并判断 INSTANCE 变量也为空,则会出现 INSTANCE 被赋值两次的情况。

线程安全的懒汉式

既然线程不安全,我们可以通过在静态方法上添加 synchronized 关键字,来进行线程同步

/**
 *  lazy loading
 *
 *  也称懒汉式
 *  虽然达到了按需初始化的目的,但却带来了线程不安全的问题
 *  可以通过 synchronized 解决问题,但也带来效率低下
 *
 */
public class Singleton03 {

    private static Singleton03 INSTANCE;

    private Singleton03(){}

    public static synchronized Singleton03 getInstance(){
        if(INSTANCE == null){
            INSTANCE = new Singleton03();
        }
        return INSTANCE;
    }


}

虽然解决了线程安全的问题,但是效率比较低下。

因为每次调用 getInstance() 方法的时候,都会加锁

线程不安全的双重检查锁定

/**
 * 双重检查锁定
 */
public class Singleton05 {

    private static Singleton05 INSTANCE;

    private Singleton05(){

    }

    public static Singleton05 getInstance(){
        if(INSTANCE == null){
            synchronized (Singleton05.class){
                if(INSTANCE == null){
                    INSTANCE = new Singleton05();
                }
            }
        }
        return INSTANCE;
    }

}

有效的解决了性能的问题,一旦 INSTANCE 被初始化,则后续的调用都不会加锁

但是这个虽然用到了同步,但是依然会有线程安全的问题

即 cpu 对指令的重排序,会出现 INSTANCE 为 null 的情况。因为 CPU 会对 new Singleton05() 的 分配内存和赋值指令排序,会有 分配内存之后被 直接返回的情况出现,所以线程不安全。

线程安全的双重检查锁定

/**
 * 双重检查锁定
 */
public class Singleton05 {

    private static volatile Singleton05 INSTANCE;

    private Singleton05(){

    }

    public static Singleton05 getInstance(){
        if(INSTANCE == null){
            synchronized (Singleton05.class){
                if(INSTANCE == null){
                    INSTANCE = new Singleton05();
                }
            }
        }
        return INSTANCE;
    }

}

只需要对 INSTANCE 变量加上 volatile 关键字,该改关键字会阻止 cpu 的指令重排序优化

静态内部类

/**
 *  静态内部类的方式
 *  JVM 保证线程安全
 *  加载外部类时不会加载内部类,这样可以实现懒加载
 */
public class Singleton06 {

    private Singleton06(){
    }

    private static class SingletonHandler{
       private static final Singleton06 INSTANCE = new Singleton06();
    }

    public static Singleton06 getInstance(){
        return SingletonHandler.INSTANCE;
    }

}

比较完美的一种方式。

枚举的方式

public enum Singleton07 {

    INSTANCE;

    private String name = "world";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖 • 1 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...