美文网首页
单例模式(Singleton Pattern)

单例模式(Singleton Pattern)

作者: lxbnjupt | 来源:发表于2018-07-31 17:30 被阅读0次

一、单例模式简介

1. 定义

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,该设计模式属于创建型模式。这种设计模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2. 特点

  • 单例类只能有一个实例;
  • 单例类必须自己创建自己的唯一实例;
  • 单例类必须给所有其他对象提供这一实例。

3. 实现关键

  • 构造函数是私有的;
  • 一个静态的变量用来保存单实例的引用;
  • 一个公有的静态方法用来获取单实例的引用,如果实例为null即创建一个。

二、单例模式实现

单例模式的实现有很多种方式,根据需求场景大致可以分为2大类。其中一类是初始化单例类时就创建单实例,另一类是延迟创建单实例(即使用时才创建,亦即懒加载)。

1. 初始化单例类时创建单实例

(1)饿汉式

该创建方式基于JVM的类加载机制,保证单实例只会被创建一次,从而避免了多线程的同步问题,是线程安全的。其优点是实现方式简单,缺点是一旦类被加载,单实例就会初始化,没有实现懒加载。

  • JVM在类的初始化阶段,即在Class被加载后、被线程使用前,会执行类的初始化;
  • 在执行类的初始化期间,JVM会去获取一个锁,可以同步多个线程对同一个类的初始化。
public class Singleton {

    // 静态变量保存单实例的引用
    private static Singleton INSTANCE = new Singleton();

    // 构造函数私有
    private Singleton() {

    }

    // 公有的静态方法用来获取单实例的引用
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

(2)枚举

该创建方式利用的枚举的特性实现单例,由JVM保证线程安全,是最简洁、易用的单例实现方式。
《Effective Java》:单元素的枚举类型已经成为实现 Singleton 的最佳方法。

public enum Singleton {
    // 定义一个枚举的元素,即为单例类的一个实例
    INSTANCE;
}

2. 延迟创建单实例(懒加载)

(1)懒汉式

该创建方式的特点是需要单例时才创建单实例,即实现懒加载。但是,这种创建方式会导致一个问题,那就是线程不安全。当多个线程并发调用getInstance方法时,可能会创建多个实例,从而导致单例模式失效,因此需要对其进行改进和优化。

public class Singleton {

    // 类加载时先不创建单例对象,单实例引用置为null
    private static Singleton INSTANCE = null;

    // 构造函数私有
    private Singleton() {

    }

    // 需要单例时才创建单实例
    public static Singleton getInstance() {
        // 先判断单实例是否为空,以避免重复创建
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

(2)同步锁(懒汉式改进)

该创建方式是对懒汉式的改进,通过使用同步锁(synchronized)机制创建单实例方法 ,避免多个线程同时调用造成单例被多次创建,从而达到线程安全的目的。但是,这种同步锁的方法又会带来性能问题,即同一时间内只有一个线程能够调用getInstance方法,其它线程则会因为被阻塞而一直等待,加锁导致耗费时间和性能,因此还需对同步锁进行再次优化。

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        // 加入同步锁
        synchronized (Singleton.class) {
            if (INSTANCE == null) {
                INSTANCE = new Singleton();
            }
        }
        return INSTANCE;
    }
}

(3)双重校验锁(同步锁改进)

该创建方式是对同步锁的改进,即在同步锁的基础上,再添加一层if判断若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能。执行两次检测很有必要的,当多线程调用时,如果多个线程同时执行完了第一次检查判断,其中一个进入同步代码块创建了实例,后面的线程因第二次检测判断就不会再创建新实例。

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            // 加入同步锁
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

但但但是,这种创建方式看起来似乎很完美,但仍旧存在问题,原因归根结底是INSTANCE = new Singleton()并非是一个原子操作。
事实上,INSTANCE = new Singleton()这句话在JVM中大概做了下面 3 件事情:
1.给 INSTANCE 分配内存;
2.调用 Singleton 的构造函数来初始化成员变量;
3.将 INSTANCE 对象指向分配的内存空间,执行完这步 INSTANCE 就不是null了。
JVM的即时编译器中存在指令重排序的优化,也就是说上面的第2步和第3步的顺序是不能保证的,最终的执行顺序可能是1-2-3也可能是1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 INSTANCE 已经不是null了(但却没有初始化),所以线程二会直接返回 INSTANCE,然后使用,接着报错。
解决方法很简单,我们只需要将 INSTANCE 变量声明成 volatile 就可以了,大概可以改成像下面的样子:

public class Singleton {

    // 声明成 volatile
    private volatile static Singleton INSTANCE;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            // 加入同步锁
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

(4)静态内部类

该创建方式使用静态内部类来创建单实例,实现了懒加载及线程安全。当Singleton被加载时,其内部类并不会被初始化,从而不会创建单实例。只有getInstance() 方法被调用时,静态内部类被加载,此时才会去创建单实例,从而实现了懒加载。同时,基于JVM的类加载机制,保证单实例只会被创建一次,从而避免了多线程的同步问题,是线程安全的。

public class Singleton {

    // 构造方法私有
    private Singleton() {

    }

    // 静态内部类
    private static class SingletonHolder {
        // 在静态内部类中创建单实例
        private static Singleton INSTANCE = new Singleton();

    }

    // 需要单例时才创建单实例
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

相关文章

网友评论

      本文标题:单例模式(Singleton Pattern)

      本文链接:https://www.haomeiwen.com/subject/usrhvftx.html